// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;

using Microsoft.Management.Infrastructure;
using Microsoft.Management.Infrastructure.Options;
using Microsoft.PowerShell;
using Microsoft.PowerShell.Cim;
using Microsoft.PowerShell.Commands;

namespace System.Management.Automation
{
    /// <summary>
    /// </summary>
    public static class CompletionCompleters
    {
        static CompletionCompleters()
        {
            AppDomain.CurrentDomain.AssemblyLoad += UpdateTypeCacheOnAssemblyLoad;
        }

        private static void UpdateTypeCacheOnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
        {
            // Just null out the cache - we'll rebuild it the next time someone tries to complete a type.
            // We could rebuild it now, but we could be loading multiple assemblies (e.g. dependent assemblies)
            // and there is no sense in rebuilding anything until we're done loading all of the assemblies.
            Interlocked.Exchange(ref s_typeCache, null);
        }

        #region Command Names

        /// <summary>
        /// </summary>
        /// <param name="commandName"></param>
        /// <returns></returns>
        public static IEnumerable<CompletionResult> CompleteCommand(string commandName)
        {
            return CompleteCommand(commandName, null);
        }

        /// <summary>
        /// </summary>
        /// <param name="commandName"></param>
        /// <param name="moduleName"></param>
        /// <param name="commandTypes"></param>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
        public static IEnumerable<CompletionResult> CompleteCommand(string commandName, string moduleName, CommandTypes commandTypes = CommandTypes.All)
        {
            var runspace = Runspace.DefaultRunspace;
            if (runspace == null)
            {
                // No runspace, just return no results.
                return CommandCompletion.EmptyCompletionResult;
            }

            var helper = new PowerShellExecutionHelper(PowerShell.Create(RunspaceMode.CurrentRunspace));
            var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext;
            return CompleteCommand(new CompletionContext { WordToComplete = commandName, Helper = helper, ExecutionContext = executionContext }, moduleName, commandTypes);
        }

        internal static List<CompletionResult> CompleteCommand(CompletionContext context)
        {
            return CompleteCommand(context, null);
        }

        private static List<CompletionResult> CompleteCommand(CompletionContext context, string moduleName, CommandTypes types = CommandTypes.All)
        {
            var addAmpersandIfNecessary = IsAmpersandNeeded(context, false);

            string commandName = context.WordToComplete;
            string quote = HandleDoubleAndSingleQuote(ref commandName);

            List<CompletionResult> commandResults = null;

            if (commandName.IndexOfAny(Utils.Separators.DirectoryOrDrive) == -1)
            {
                // The name to complete is neither module qualified nor is it a relative/rooted file path.

                Ast lastAst = null;
                if (context.RelatedAsts != null && context.RelatedAsts.Count > 0)
                {
                    lastAst = context.RelatedAsts.Last();
                }

                commandResults = ExecuteGetCommandCommand(useModulePrefix: false);

                if (lastAst != null)
                {
                    // We need to add the wildcard to the end so the regex is built correctly.
                    commandName += "*";

                    // Search the asts for function definitions that we might be calling
                    var findFunctionsVisitor = new FindFunctionsVisitor();
                    while (lastAst.Parent != null)
                    {
                        lastAst = lastAst.Parent;
                    }

                    lastAst.Visit(findFunctionsVisitor);

                    WildcardPattern commandNamePattern = WildcardPattern.Get(commandName, WildcardOptions.IgnoreCase);
                    foreach (var defn in findFunctionsVisitor.FunctionDefinitions)
                    {
                        if (commandNamePattern.IsMatch(defn.Name)
                            && !commandResults.Any(cr => cr.CompletionText.Equals(defn.Name, StringComparison.OrdinalIgnoreCase)))
                        {
                            // Results found in the current script are prepended to show up at the top of the list.
                            commandResults.Insert(0, GetCommandNameCompletionResult(defn.Name, defn, addAmpersandIfNecessary, quote));
                        }
                    }
                }
            }
            else
            {
                // If there is a single \, we might be looking for a module/snapin qualified command
                var indexOfFirstColon = commandName.IndexOf(':');
                var indexOfFirstBackslash = commandName.IndexOf('\\');
                if (indexOfFirstBackslash > 0 && (indexOfFirstBackslash < indexOfFirstColon || indexOfFirstColon == -1))
                {
                    // First try the name before the backslash as a module name.
                    // Use the exact module name provided by the user
                    moduleName = commandName.Substring(0, indexOfFirstBackslash);
                    commandName = commandName.Substring(indexOfFirstBackslash + 1);

                    commandResults = ExecuteGetCommandCommand(useModulePrefix: true);
                }
            }

            return commandResults;

            List<CompletionResult> ExecuteGetCommandCommand(bool useModulePrefix)
            {
                var powershell = context.Helper
                    .AddCommandWithPreferenceSetting("Get-Command", typeof(GetCommandCommand))
                    .AddParameter("All")
                    .AddParameter("Name", commandName + "*");

                if (moduleName != null)
                {
                    powershell.AddParameter("Module", moduleName);
                }

                if (!types.Equals(CommandTypes.All))
                {
                    powershell.AddParameter("CommandType", types);
                }

                // Exception is ignored, the user simply does not get any completion results if the pipeline fails
                Exception exceptionThrown;
                var commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown);

                if (commandInfos == null || commandInfos.Count == 0)
                {
                    powershell.Commands.Clear();
                    powershell
                        .AddCommandWithPreferenceSetting("Get-Command", typeof(GetCommandCommand))
                        .AddParameter("All")
                        .AddParameter("Name", commandName)
                        .AddParameter("UseAbbreviationExpansion");

                    if (moduleName != null)
                    {
                        powershell.AddParameter("Module", moduleName);
                    }

                    if (!types.Equals(CommandTypes.All))
                    {
                        powershell.AddParameter("CommandType", types);
                    }

                    commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown);
                }

                List<CompletionResult> completionResults = null;

                if (commandInfos != null && commandInfos.Count > 1)
                {
                    // OrderBy is using stable sorting
                    var sortedCommandInfos = commandInfos.OrderBy(a => a, new CommandNameComparer());
                    completionResults = MakeCommandsUnique(sortedCommandInfos, useModulePrefix, addAmpersandIfNecessary, quote);
                }
                else
                {
                    completionResults = MakeCommandsUnique(commandInfos, useModulePrefix, addAmpersandIfNecessary, quote);
                }

                return completionResults;
            }
        }

        private static readonly HashSet<string> s_keywordsToExcludeFromAddingAmpersand
            = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { nameof(TokenKind.InlineScript), nameof(TokenKind.Configuration) };

        internal static CompletionResult GetCommandNameCompletionResult(string name, object command, bool addAmpersandIfNecessary, string quote)
        {
            string syntax = name, listItem = name;

            var commandInfo = command as CommandInfo;
            if (commandInfo != null)
            {
                try
                {
                    listItem = commandInfo.Name;
                    // This may require parsing a script, which could fail in a number of different ways
                    // (syntax errors, security exceptions, etc.)  If so, the name is fine for the tooltip.
                    syntax = commandInfo.Syntax;
                }
                catch (Exception)
                {
                }
            }

            syntax = string.IsNullOrEmpty(syntax) ? name : syntax;
            bool needAmpersand;

            if (CompletionRequiresQuotes(name, false))
            {
                needAmpersand = quote == string.Empty && addAmpersandIfNecessary;
                string quoteInUse = quote == string.Empty ? "'" : quote;
                if (quoteInUse == "'")
                {
                    name = name.Replace("'", "''");
                }
                else
                {
                    name = name.Replace("`", "``");
                    name = name.Replace("$", "`$");
                }

                name = quoteInUse + name + quoteInUse;
            }
            else
            {
                needAmpersand = quote == string.Empty && addAmpersandIfNecessary &&
                                Tokenizer.IsKeyword(name) && !s_keywordsToExcludeFromAddingAmpersand.Contains(name);
                name = quote + name + quote;
            }

            // It's useless to call ForEach-Object (foreach) as the first command of a pipeline. For example:
            //     PS C:\> fore<tab>  --->   PS C:\> foreach   (expected, use as the keyword)
            //     PS C:\> fore<tab>  --->   PS C:\> & foreach (unexpected, ForEach-Object is seldom used as the first command of a pipeline)
            if (needAmpersand && name != SpecialVariables.@foreach)
            {
                name = "& " + name;
            }

            return new CompletionResult(name, listItem, CompletionResultType.Command, syntax);
        }

        internal static List<CompletionResult> MakeCommandsUnique(IEnumerable<PSObject> commandInfoPsObjs, bool includeModulePrefix, bool addAmpersandIfNecessary, string quote)
        {
            List<CompletionResult> results = new List<CompletionResult>();
            if (commandInfoPsObjs == null || !commandInfoPsObjs.Any())
            {
                return results;
            }

            var commandTable = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            foreach (var psobj in commandInfoPsObjs)
            {
                object baseObj = PSObject.Base(psobj);
                string name = null;

                var commandInfo = baseObj as CommandInfo;
                if (commandInfo != null)
                {
                    // Skip the private commands
                    if (commandInfo.Visibility == SessionStateEntryVisibility.Private) { continue; }

                    name = commandInfo.Name;
                    if (includeModulePrefix && !string.IsNullOrEmpty(commandInfo.ModuleName))
                    {
                        // The command might be a prefixed commandInfo that we get by importing a module with the -Prefix parameter, for example:
                        //    FooModule.psm1: Get-Foo
                        //    import-module FooModule -Prefix PowerShell
                        //    --> command 'Get-PowerShellFoo' in the global session state (prefixed commandInfo)
                        //        command 'Get-Foo' in the module session state (un-prefixed commandInfo)
                        // in that case, we should not add the module name qualification because it doesn't work
                        if (string.IsNullOrEmpty(commandInfo.Prefix) || !ModuleCmdletBase.IsPrefixedCommand(commandInfo))
                        {
                            name = commandInfo.ModuleName + "\\" + commandInfo.Name;
                        }
                    }
                }
                else
                {
                    name = baseObj as string;
                    if (name == null) { continue; }
                }

                object value;
                if (!commandTable.TryGetValue(name, out value))
                {
                    commandTable.Add(name, baseObj);
                }
                else
                {
                    var list = value as List<object>;
                    if (list != null)
                    {
                        list.Add(baseObj);
                    }
                    else
                    {
                        list = new List<object> { value, baseObj };
                        commandTable[name] = list;
                    }
                }
            }

            List<CompletionResult> endResults = null;
            foreach (var keyValuePair in commandTable)
            {
                var commandList = keyValuePair.Value as List<object>;
                if (commandList != null)
                {
                    if (endResults == null)
                    {
                        endResults = new List<CompletionResult>();
                    }

                    // The first command might be an un-prefixed commandInfo that we get by importing a module with the -Prefix parameter,
                    // in that case, we should add the module name qualification because if the module is not in the module path, calling
                    // 'Get-Foo' directly doesn't work
                    string completionName = keyValuePair.Key;
                    if (!includeModulePrefix)
                    {
                        var commandInfo = commandList[0] as CommandInfo;
                        if (commandInfo != null && !string.IsNullOrEmpty(commandInfo.Prefix))
                        {
                            Diagnostics.Assert(!string.IsNullOrEmpty(commandInfo.ModuleName), "the module name should exist if commandInfo.Prefix is not an empty string");
                            if (!ModuleCmdletBase.IsPrefixedCommand(commandInfo))
                            {
                                completionName = commandInfo.ModuleName + "\\" + completionName;
                            }
                        }
                    }

                    results.Add(GetCommandNameCompletionResult(completionName, commandList[0], addAmpersandIfNecessary, quote));

                    // For the other commands that are hidden, we need to disambiguate,
                    // but put these at the end as it's less likely any of the hidden
                    // commands are desired.  If we can't add anything to disambiguate,
                    // then we'll skip adding a completion result.
                    for (int index = 1; index < commandList.Count; index++)
                    {
                        var commandInfo = commandList[index] as CommandInfo;
                        Diagnostics.Assert(commandInfo != null, "Elements should always be CommandInfo");

                        if (commandInfo.CommandType == CommandTypes.Application)
                        {
                            endResults.Add(GetCommandNameCompletionResult(commandInfo.Definition, commandInfo, addAmpersandIfNecessary, quote));
                        }
                        else if (!string.IsNullOrEmpty(commandInfo.ModuleName))
                        {
                            var name = commandInfo.ModuleName + "\\" + commandInfo.Name;
                            endResults.Add(GetCommandNameCompletionResult(name, commandInfo, addAmpersandIfNecessary, quote));
                        }
                    }
                }
                else
                {
                    // The first command might be an un-prefixed commandInfo that we get by importing a module with the -Prefix parameter,
                    // in that case, we should add the module name qualification because if the module is not in the module path, calling
                    // 'Get-Foo' directly doesn't work
                    string completionName = keyValuePair.Key;
                    if (!includeModulePrefix)
                    {
                        var commandInfo = keyValuePair.Value as CommandInfo;
                        if (commandInfo != null && !string.IsNullOrEmpty(commandInfo.Prefix))
                        {
                            Diagnostics.Assert(!string.IsNullOrEmpty(commandInfo.ModuleName), "the module name should exist if commandInfo.Prefix is not an empty string");
                            if (!ModuleCmdletBase.IsPrefixedCommand(commandInfo))
                            {
                                completionName = commandInfo.ModuleName + "\\" + completionName;
                            }
                        }
                    }

                    results.Add(GetCommandNameCompletionResult(completionName, keyValuePair.Value, addAmpersandIfNecessary, quote));
                }
            }

            if (endResults != null && endResults.Count > 0)
            {
                results.AddRange(endResults);
            }

            return results;
        }

        private class FindFunctionsVisitor : AstVisitor
        {
            internal readonly List<FunctionDefinitionAst> FunctionDefinitions = new List<FunctionDefinitionAst>();

            public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
            {
                FunctionDefinitions.Add(functionDefinitionAst);
                return AstVisitAction.Continue;
            }
        }

        #endregion Command Names

        #region Module Names

        internal static List<CompletionResult> CompleteModuleName(CompletionContext context, bool loadedModulesOnly, bool skipEditionCheck = false)
        {
            var moduleName = context.WordToComplete ?? string.Empty;
            var result = new List<CompletionResult>();
            var quote = HandleDoubleAndSingleQuote(ref moduleName);

            if (!moduleName.EndsWith('*'))
            {
                moduleName += "*";
            }

            var powershell = context.Helper.AddCommandWithPreferenceSetting("Get-Module", typeof(GetModuleCommand)).AddParameter("Name", moduleName);
            if (!loadedModulesOnly)
            {
                powershell.AddParameter("ListAvailable", true);

                // -SkipEditionCheck should only be set or apply to -ListAvailable
                if (skipEditionCheck)
                {
                    powershell.AddParameter("SkipEditionCheck", true);
                }
            }

            Exception exceptionThrown;
            var psObjects = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown);

            if (psObjects != null)
            {
                foreach (dynamic moduleInfo in psObjects)
                {
                    var completionText = moduleInfo.Name.ToString();
                    var listItemText = completionText;
                    var toolTip = "Description: " + moduleInfo.Description.ToString() + "\r\nModuleType: "
                                  + moduleInfo.ModuleType.ToString() + "\r\nPath: "
                                  + moduleInfo.Path.ToString();

                    if (CompletionRequiresQuotes(completionText, false))
                    {
                        var quoteInUse = quote == string.Empty ? "'" : quote;
                        if (quoteInUse == "'")
                            completionText = completionText.Replace("'", "''");
                        completionText = quoteInUse + completionText + quoteInUse;
                    }
                    else
                    {
                        completionText = quote + completionText + quote;
                    }

                    result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, toolTip));
                }
            }

            return result;
        }

        #endregion Module Names

        #region Command Parameters
        private static string[] s_parameterNamesOfImportDSCResource = { "Name", "ModuleName", "ModuleVersion" };

        internal static List<CompletionResult> CompleteCommandParameter(CompletionContext context)
        {
            string partialName = null;
            bool withColon = false;
            CommandAst commandAst = null;
            List<CompletionResult> result = new List<CompletionResult>();

            // Find the parameter ast, it will be near or at the end
            CommandParameterAst parameterAst = null;
            DynamicKeywordStatementAst keywordAst = null;
            for (int i = context.RelatedAsts.Count - 1; i >= 0; i--)
            {
                if (keywordAst == null)
                    keywordAst = context.RelatedAsts[i] as DynamicKeywordStatementAst;
                parameterAst = (context.RelatedAsts[i] as CommandParameterAst);
                if (parameterAst != null) break;
            }

            if (parameterAst != null)
            {
                keywordAst = parameterAst.Parent as DynamicKeywordStatementAst;
            }

            // If parent is DynamicKeywordStatementAst - 'Import-DscResource',
            // then customize the auto completion results
            if (keywordAst != null && string.Equals(keywordAst.Keyword.Keyword, "Import-DscResource", StringComparison.OrdinalIgnoreCase)
                && !string.IsNullOrWhiteSpace(context.WordToComplete) && context.WordToComplete.StartsWith('-'))
            {
                var lastAst = context.RelatedAsts.Last();
                var wordToMatch = context.WordToComplete.Substring(1) + "*";
                var pattern = WildcardPattern.Get(wordToMatch, WildcardOptions.IgnoreCase);
                var parameterNames = keywordAst.CommandElements.Where(ast => ast is CommandParameterAst).Select(ast => (ast as CommandParameterAst).ParameterName);
                foreach (var parameterName in s_parameterNamesOfImportDSCResource)
                {
                    if (pattern.IsMatch(parameterName) && !parameterNames.Contains(parameterName, StringComparer.OrdinalIgnoreCase))
                    {
                        string tooltip = "[String] " + parameterName;
                        result.Add(new CompletionResult("-" + parameterName, parameterName, CompletionResultType.ParameterName, tooltip));
                    }
                }

                if (result.Count > 0)
                {
                    context.ReplacementLength = context.WordToComplete.Length;
                    context.ReplacementIndex = lastAst.Extent.StartOffset;
                }

                return result;
            }

            if (parameterAst != null)
            {
                // Parent must be a command
                commandAst = (CommandAst)parameterAst.Parent;
                partialName = parameterAst.ParameterName;
                withColon = context.WordToComplete.EndsWith(':');
            }
            else
            {
                // No CommandParameterAst is found. It could be a StringConstantExpressionAst "-"
                var dashAst = (context.RelatedAsts[context.RelatedAsts.Count - 1] as StringConstantExpressionAst);
                if (dashAst == null)
                    return result;
                if (!dashAst.Value.Trim().Equals("-", StringComparison.OrdinalIgnoreCase))
                    return result;

                // Parent must be a command
                commandAst = (CommandAst)dashAst.Parent;
                partialName = string.Empty;
            }

            PseudoBindingInfo pseudoBinding = new PseudoParameterBinder()
                                                .DoPseudoParameterBinding(commandAst, null, parameterAst, PseudoParameterBinder.BindingType.ParameterCompletion);
            // The command cannot be found or it's not a cmdlet, not a script cmdlet, not a function.
            // Try completing as if it the parameter is a command argument for native command completion.
            if (pseudoBinding == null)
            {
                return CompleteCommandArgument(context);
            }

            switch (pseudoBinding.InfoType)
            {
                case PseudoBindingInfoType.PseudoBindingFail:
                    // The command is a cmdlet or script cmdlet. Binding failed
                    result = GetParameterCompletionResults(partialName, uint.MaxValue, pseudoBinding.UnboundParameters, withColon);
                    break;
                case PseudoBindingInfoType.PseudoBindingSucceed:
                    // The command is a cmdlet or script cmdlet. Binding succeeded.
                    result = GetParameterCompletionResults(partialName, pseudoBinding, parameterAst, withColon);
                    break;
            }

            if (result.Count == 0)
            {
                result = pseudoBinding.CommandName.Equals("Set-Location", StringComparison.OrdinalIgnoreCase)
                             ? new List<CompletionResult>(CompleteFilename(context, containerOnly: true, extension: null))
                             : new List<CompletionResult>(CompleteFilename(context));
            }

            return result;
        }

        /// <summary>
        /// Get the parameter completion results when the pseudo binding was successful.
        /// </summary>
        /// <param name="parameterName"></param>
        /// <param name="bindingInfo"></param>
        /// <param name="parameterAst"></param>
        /// <param name="withColon"></param>
        /// <returns></returns>
        private static List<CompletionResult> GetParameterCompletionResults(string parameterName, PseudoBindingInfo bindingInfo, CommandParameterAst parameterAst, bool withColon)
        {
            Diagnostics.Assert(bindingInfo.InfoType.Equals(PseudoBindingInfoType.PseudoBindingSucceed), "The pseudo binding should succeed");
            List<CompletionResult> result = new List<CompletionResult>();

            if (parameterName == string.Empty)
            {
                result = GetParameterCompletionResults(
                    parameterName,
                    bindingInfo.ValidParameterSetsFlags,
                    bindingInfo.UnboundParameters,
                    withColon);
                return result;
            }

            if (bindingInfo.ParametersNotFound.Count > 0)
            {
                // The parameter name cannot be matched to any parameter
                if (bindingInfo.ParametersNotFound.Any(pAst => parameterAst.GetHashCode() == pAst.GetHashCode()))
                {
                    return result;
                }
            }

            if (bindingInfo.AmbiguousParameters.Count > 0)
            {
                // The parameter name is ambiguous. It's ignored in the pseudo binding, and we should search in the UnboundParameters
                if (bindingInfo.AmbiguousParameters.Any(pAst => parameterAst.GetHashCode() == pAst.GetHashCode()))
                {
                    result = GetParameterCompletionResults(
                        parameterName,
                        bindingInfo.ValidParameterSetsFlags,
                        bindingInfo.UnboundParameters,
                        withColon);
                }

                return result;
            }

            if (bindingInfo.DuplicateParameters.Count > 0)
            {
                // The parameter name is resolved to a parameter that is already bound. We search it in the BoundParameters
                if (bindingInfo.DuplicateParameters.Any(pAst => parameterAst.GetHashCode() == pAst.Parameter.GetHashCode()))
                {
                    result = GetParameterCompletionResults(
                        parameterName,
                        bindingInfo.ValidParameterSetsFlags,
                        bindingInfo.BoundParameters.Values,
                        withColon);
                }

                return result;
            }

            // The parameter should be bound in the pseudo binding during the named binding
            string matchedParameterName = null;
            foreach (KeyValuePair<string, AstParameterArgumentPair> entry in bindingInfo.BoundArguments)
            {
                switch (entry.Value.ParameterArgumentType)
                {
                    case AstParameterArgumentType.AstPair:
                        {
                            AstPair pair = (AstPair)entry.Value;
                            if (pair.ParameterSpecified && pair.Parameter.GetHashCode() == parameterAst.GetHashCode())
                            {
                                matchedParameterName = entry.Key;
                            }
                            else if (pair.ArgumentIsCommandParameterAst && pair.Argument.GetHashCode() == parameterAst.GetHashCode())
                            {
                                // The parameter name cannot be resolved to a parameter
                                return result;
                            }
                        }

                        break;
                    case AstParameterArgumentType.Fake:
                        {
                            FakePair pair = (FakePair)entry.Value;
                            if (pair.ParameterSpecified && pair.Parameter.GetHashCode() == parameterAst.GetHashCode())
                            {
                                matchedParameterName = entry.Key;
                            }
                        }

                        break;
                    case AstParameterArgumentType.Switch:
                        {
                            SwitchPair pair = (SwitchPair)entry.Value;
                            if (pair.ParameterSpecified && pair.Parameter.GetHashCode() == parameterAst.GetHashCode())
                            {
                                matchedParameterName = entry.Key;
                            }
                        }

                        break;
                    case AstParameterArgumentType.AstArray:
                    case AstParameterArgumentType.PipeObject:
                        break;
                }

                if (matchedParameterName != null)
                    break;
            }

            Diagnostics.Assert(matchedParameterName != null, "we should find matchedParameterName from the BoundArguments");
            MergedCompiledCommandParameter param = bindingInfo.BoundParameters[matchedParameterName];

            WildcardPattern pattern = WildcardPattern.Get(parameterName + "*", WildcardOptions.IgnoreCase);
            string parameterType = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] ";
            string colonSuffix = withColon ? ":" : string.Empty;
            if (pattern.IsMatch(matchedParameterName))
            {
                string completionText = "-" + matchedParameterName + colonSuffix;
                string tooltip = parameterType + matchedParameterName;
                result.Add(new CompletionResult(completionText, matchedParameterName, CompletionResultType.ParameterName, tooltip));
            }

            // Process alias when there is partial input
            result.AddRange(from alias in param.Parameter.Aliases
                            where pattern.IsMatch(alias)
                            select
                                new CompletionResult("-" + alias + colonSuffix, alias, CompletionResultType.ParameterName,
                                                     parameterType + alias));

            return result;
        }

        /// <summary>
        /// Get the parameter completion results by using the given valid parameter sets and available parameters.
        /// </summary>
        /// <param name="parameterName"></param>
        /// <param name="validParameterSetFlags"></param>
        /// <param name="parameters"></param>
        /// <param name="withColon"></param>
        /// <returns></returns>
        private static List<CompletionResult> GetParameterCompletionResults(
            string parameterName,
            uint validParameterSetFlags,
            IEnumerable<MergedCompiledCommandParameter> parameters,
            bool withColon)
        {
            var result = new List<CompletionResult>();
            var commonParamResult = new List<CompletionResult>();
            var pattern = WildcardPattern.Get(parameterName + "*", WildcardOptions.IgnoreCase);
            var colonSuffix = withColon ? ":" : string.Empty;

            bool addCommonParameters = true;
            foreach (MergedCompiledCommandParameter param in parameters)
            {
                bool inParameterSet = (param.Parameter.ParameterSetFlags & validParameterSetFlags) != 0 || param.Parameter.IsInAllSets;
                if (!inParameterSet)
                    continue;

                string name = param.Parameter.Name;
                string type = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] ";
                bool isCommonParameter = Cmdlet.CommonParameters.Contains(name, StringComparer.OrdinalIgnoreCase);
                List<CompletionResult> listInUse = isCommonParameter ? commonParamResult : result;

                if (pattern.IsMatch(name))
                {
                    // Then using functions to back dynamic keywords, we don't necessarily
                    // want all of the parameters to be shown to the user. Those that are marked
                    // DontShow will not be displayed. Also, if any of the parameters have
                    // don't show set, we won't show any of the common parameters either.
                    bool showToUser = true;
                    var compiledAttributes = param.Parameter.CompiledAttributes;
                    if (compiledAttributes != null && compiledAttributes.Count > 0)
                    {
                        foreach (var attr in compiledAttributes)
                        {
                            var pattr = attr as ParameterAttribute;
                            if (pattr != null && pattr.DontShow)
                            {
                                showToUser = false;
                                addCommonParameters = false;
                                break;
                            }
                        }
                    }

                    if (showToUser)
                    {
                        string completionText = "-" + name + colonSuffix;
                        string tooltip = type + name;
                        listInUse.Add(new CompletionResult(completionText, name, CompletionResultType.ParameterName,
                                                           tooltip));
                    }
                }

                if (parameterName != string.Empty)
                {
                    // Process alias when there is partial input
                    listInUse.AddRange(from alias in param.Parameter.Aliases
                                       where pattern.IsMatch(alias)
                                       select
                                         new CompletionResult("-" + alias + colonSuffix, alias, CompletionResultType.ParameterName,
                                                              type + alias));
                }
            }

            // Add the common parameters to the results if expected.
            if (addCommonParameters)
            {
                result.AddRange(commonParamResult);
            }

            return result;
        }

        /// <summary>
        /// Get completion results for operators that start with <paramref name="wordToComplete"/>
        /// </summary>
        /// <param name="wordToComplete">The starting text of the operator to complete.</param>
        /// <returns>A list of completion results.</returns>
        public static List<CompletionResult> CompleteOperator(string wordToComplete)
        {
            if (wordToComplete.StartsWith('-'))
            {
                wordToComplete = wordToComplete.Substring(1);
            }

            return (from op in Tokenizer._operatorText
                    where op.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)
                    orderby op
                    select new CompletionResult("-" + op, op, CompletionResultType.ParameterName, GetOperatorDescription(op))).ToList();
        }

        private static string GetOperatorDescription(string op)
        {
            return ResourceManagerCache.GetResourceString(typeof(CompletionCompleters).Assembly,
                                                          "System.Management.Automation.resources.TabCompletionStrings",
                                                          op + "OperatorDescription");
        }

        #endregion Command Parameters

        #region Command Arguments

        internal static List<CompletionResult> CompleteCommandArgument(CompletionContext context)
        {
            CommandAst commandAst = null;
            List<CompletionResult> result = new List<CompletionResult>();

            // Find the expression ast. It should be at the end if there is one
            ExpressionAst expressionAst = null;
            MemberExpressionAst secondToLastMemberAst = null;
            Ast lastAst = context.RelatedAsts.Last();

            expressionAst = lastAst as ExpressionAst;
            if (expressionAst != null)
            {
                if (expressionAst.Parent is CommandAst)
                {
                    commandAst = (CommandAst)expressionAst.Parent;

                    if (expressionAst is ErrorExpressionAst && expressionAst.Extent.Text.EndsWith(','))
                    {
                        context.WordToComplete = string.Empty;
                        // BUGBUG context.CursorPosition = expressionAst.Extent.StartScriptPosition;
                    }
                    else if (commandAst.CommandElements.Count == 1 || context.WordToComplete == string.Empty)
                    {
                        expressionAst = null;
                    }
                    else if (commandAst.CommandElements.Count > 2)
                    {
                        var length = commandAst.CommandElements.Count;
                        var index = 1;

                        for (; index < length; index++)
                        {
                            if (commandAst.CommandElements[index] == expressionAst)
                                break;
                        }

                        CommandElementAst secondToLastAst = null;
                        if (index > 1)
                        {
                            secondToLastAst = commandAst.CommandElements[index - 1];
                            secondToLastMemberAst = secondToLastAst as MemberExpressionAst;
                        }

                        var partialPathAst = expressionAst as StringConstantExpressionAst;
                        if (partialPathAst != null && secondToLastAst != null &&
                            partialPathAst.StringConstantType == StringConstantType.BareWord &&
                            secondToLastAst.Extent.EndLineNumber == partialPathAst.Extent.StartLineNumber &&
                            secondToLastAst.Extent.EndColumnNumber == partialPathAst.Extent.StartColumnNumber &&
                            partialPathAst.Value.IndexOfAny(Utils.Separators.Directory) == 0)
                        {
                            var secondToLastStringConstantAst = secondToLastAst as StringConstantExpressionAst;
                            var secondToLastExpandableStringAst = secondToLastAst as ExpandableStringExpressionAst;
                            var secondToLastArrayAst = secondToLastAst as ArrayLiteralAst;
                            var secondToLastParamAst = secondToLastAst as CommandParameterAst;

                            if (secondToLastStringConstantAst != null || secondToLastExpandableStringAst != null)
                            {
                                var fullPath = ConcatenateStringPathArguments(secondToLastAst, partialPathAst.Value, context);
                                expressionAst = secondToLastStringConstantAst != null
                                                    ? (ExpressionAst)secondToLastStringConstantAst
                                                    : (ExpressionAst)secondToLastExpandableStringAst;

                                context.ReplacementIndex = ((InternalScriptPosition)secondToLastAst.Extent.StartScriptPosition).Offset;
                                context.ReplacementLength += ((InternalScriptPosition)secondToLastAst.Extent.EndScriptPosition).Offset - context.ReplacementIndex;
                                context.WordToComplete = fullPath;
                                // context.CursorPosition = secondToLastAst.Extent.StartScriptPosition;
                            }
                            else if (secondToLastArrayAst != null)
                            {
                                // Handle cases like: dir -Path .\cd, 'a b'\new<tab>
                                var lastArrayElement = secondToLastArrayAst.Elements.LastOrDefault();
                                var fullPath = ConcatenateStringPathArguments(lastArrayElement, partialPathAst.Value, context);
                                if (fullPath != null)
                                {
                                    expressionAst = secondToLastArrayAst;

                                    context.ReplacementIndex = ((InternalScriptPosition)lastArrayElement.Extent.StartScriptPosition).Offset;
                                    context.ReplacementLength += ((InternalScriptPosition)lastArrayElement.Extent.EndScriptPosition).Offset - context.ReplacementIndex;
                                    context.WordToComplete = fullPath;
                                }
                            }
                            else if (secondToLastParamAst != null)
                            {
                                // Handle cases like: dir -Path: .\cd, 'a b'\new<tab> || dir -Path: 'a b'\new<tab>
                                var fullPath = ConcatenateStringPathArguments(secondToLastParamAst.Argument, partialPathAst.Value, context);
                                if (fullPath != null)
                                {
                                    expressionAst = secondToLastParamAst.Argument;

                                    context.ReplacementIndex = ((InternalScriptPosition)secondToLastParamAst.Argument.Extent.StartScriptPosition).Offset;
                                    context.ReplacementLength += ((InternalScriptPosition)secondToLastParamAst.Argument.Extent.EndScriptPosition).Offset - context.ReplacementIndex;
                                    context.WordToComplete = fullPath;
                                }
                                else
                                {
                                    var arrayArgAst = secondToLastParamAst.Argument as ArrayLiteralAst;
                                    if (arrayArgAst != null)
                                    {
                                        var lastArrayElement = arrayArgAst.Elements.LastOrDefault();
                                        fullPath = ConcatenateStringPathArguments(lastArrayElement, partialPathAst.Value, context);
                                        if (fullPath != null)
                                        {
                                            expressionAst = arrayArgAst;

                                            context.ReplacementIndex = ((InternalScriptPosition)lastArrayElement.Extent.StartScriptPosition).Offset;
                                            context.ReplacementLength += ((InternalScriptPosition)lastArrayElement.Extent.EndScriptPosition).Offset - context.ReplacementIndex;
                                            context.WordToComplete = fullPath;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                else if (expressionAst.Parent is ArrayLiteralAst && expressionAst.Parent.Parent is CommandAst)
                {
                    commandAst = (CommandAst)expressionAst.Parent.Parent;

                    if (commandAst.CommandElements.Count == 1 || context.WordToComplete == string.Empty)
                    {
                        // dir -Path a.txt, b.txt <tab>
                        expressionAst = null;
                    }
                    else
                    {
                        // dir -Path a.txt, b.txt c<tab>
                        expressionAst = (ExpressionAst)expressionAst.Parent;
                    }
                }
                else if (expressionAst.Parent is ArrayLiteralAst && expressionAst.Parent.Parent is CommandParameterAst)
                {
                    // Handle scenarios such as
                    //      dir -Path: a.txt, <tab> || dir -Path: a.txt, b.txt <tab>
                    commandAst = (CommandAst)expressionAst.Parent.Parent.Parent;
                    if (context.WordToComplete == string.Empty)
                    {
                        // dir -Path: a.txt, b.txt <tab>
                        expressionAst = null;
                    }
                    else
                    {
                        // dir -Path: a.txt, b<tab>
                        expressionAst = (ExpressionAst)expressionAst.Parent;
                    }
                }
                else if (expressionAst.Parent is CommandParameterAst && expressionAst.Parent.Parent is CommandAst)
                {
                    commandAst = (CommandAst)expressionAst.Parent.Parent;
                    if (expressionAst is ErrorExpressionAst && expressionAst.Extent.Text.EndsWith(','))
                    {
                        // dir -Path: a.txt,<tab>
                        context.WordToComplete = string.Empty;
                        // context.CursorPosition = expressionAst.Extent.StartScriptPosition;
                    }
                    else if (context.WordToComplete == string.Empty)
                    {
                        // Handle scenario like this: Set-ExecutionPolicy -Scope:CurrentUser <tab>
                        expressionAst = null;
                    }
                }
            }
            else
            {
                var paramAst = lastAst as CommandParameterAst;
                if (paramAst != null)
                {
                    commandAst = paramAst.Parent as CommandAst;
                }
                else
                {
                    commandAst = lastAst as CommandAst;
                }
            }

            if (commandAst == null)
            {
                // We don't know if this could be expanded into anything interesting
                return result;
            }

            PseudoBindingInfo pseudoBinding = new PseudoParameterBinder()
                                                .DoPseudoParameterBinding(commandAst, null, null, PseudoParameterBinder.BindingType.ArgumentCompletion);

            do
            {
                // The command cannot be found, or it's NOT a cmdlet, NOT a script cmdlet and NOT a function
                if (pseudoBinding == null)
                    break;

                bool parsedArgumentsProvidesMatch = false;

                if (pseudoBinding.AllParsedArguments != null && pseudoBinding.AllParsedArguments.Count > 0)
                {
                    ArgumentLocation argLocation;
                    bool treatAsExpression = false;

                    if (expressionAst != null)
                    {
                        treatAsExpression = true;
                        var dashExp = expressionAst as StringConstantExpressionAst;
                        if (dashExp != null && dashExp.Value.Trim().Equals("-", StringComparison.OrdinalIgnoreCase))
                        {
                            // "-" is represented as StringConstantExpressionAst. Most likely the user is typing a <tab>
                            // after it, so in the pseudo binder, we ignore it to avoid treating it as an argument.
                            // for example:
                            //      Get-Content -Path "-<tab>  -->  Get-Content -Path ".\-patt.txt"
                            treatAsExpression = false;
                        }
                    }

                    if (treatAsExpression)
                    {
                        argLocation = FindTargetArgumentLocation(
                            pseudoBinding.AllParsedArguments, expressionAst);
                    }
                    else
                    {
                        argLocation = FindTargetArgumentLocation(
                            pseudoBinding.AllParsedArguments, context.TokenAtCursor ?? context.TokenBeforeCursor);
                    }

                    if (argLocation != null)
                    {
                        context.PseudoBindingInfo = pseudoBinding;
                        switch (pseudoBinding.InfoType)
                        {
                            case PseudoBindingInfoType.PseudoBindingSucceed:
                                result = GetArgumentCompletionResultsWithSuccessfulPseudoBinding(context, argLocation, commandAst);
                                break;
                            case PseudoBindingInfoType.PseudoBindingFail:
                                result = GetArgumentCompletionResultsWithFailedPseudoBinding(context, argLocation, commandAst);
                                break;
                        }

                        parsedArgumentsProvidesMatch = true;
                    }
                }

                if (!parsedArgumentsProvidesMatch)
                {
                    int index = 0;
                    CommandElementAst prevElem = null;
                    if (expressionAst != null)
                    {
                        foreach (CommandElementAst eleAst in commandAst.CommandElements)
                        {
                            if (eleAst.GetHashCode() == expressionAst.GetHashCode())
                                break;
                            prevElem = eleAst;
                            index++;
                        }
                    }
                    else
                    {
                        var token = context.TokenAtCursor ?? context.TokenBeforeCursor;
                        foreach (CommandElementAst eleAst in commandAst.CommandElements)
                        {
                            if (eleAst.Extent.StartOffset > token.Extent.EndOffset)
                                break;
                            prevElem = eleAst;
                            index++;
                        }
                    }

                    // positional argument with position 0
                    if (index == 1)
                    {
                        CompletePositionalArgument(
                            pseudoBinding.CommandName,
                            commandAst,
                            context,
                            result,
                            pseudoBinding.UnboundParameters,
                            pseudoBinding.DefaultParameterSetFlag,
                            uint.MaxValue,
                            0);
                    }
                    else
                    {
                        if (prevElem is CommandParameterAst && ((CommandParameterAst)prevElem).Argument == null)
                        {
                            var paramName = ((CommandParameterAst)prevElem).ParameterName;
                            var pattern = WildcardPattern.Get(paramName + "*", WildcardOptions.IgnoreCase);
                            foreach (MergedCompiledCommandParameter param in pseudoBinding.UnboundParameters)
                            {
                                if (pattern.IsMatch(param.Parameter.Name))
                                {
                                    ProcessParameter(pseudoBinding.CommandName, commandAst, context, result, param);
                                    break;
                                }

                                var isAliasMatch = false;
                                foreach (string alias in param.Parameter.Aliases)
                                {
                                    if (pattern.IsMatch(alias))
                                    {
                                        isAliasMatch = true;
                                        ProcessParameter(pseudoBinding.CommandName, commandAst, context, result, param);
                                        break;
                                    }
                                }

                                if (isAliasMatch)
                                    break;
                            }
                        }
                    }
                }
            } while (false);

            // Indicate if the current argument completion falls into those pre-defined cases and
            // has been processed already.
            bool hasBeenProcessed = false;
            if (result.Count > 0 && result[result.Count - 1].Equals(CompletionResult.Null))
            {
                result.RemoveAt(result.Count - 1);
                hasBeenProcessed = true;

                if (result.Count > 0)
                    return result;
            }

            // Handle some special cases such as:
            //    & "get-comm<tab> --> & "Get-Command"
            //    & "sa<tab>       --> & ".\sa[v].txt"
            if (expressionAst == null && !hasBeenProcessed &&
                commandAst.CommandElements.Count == 1 &&
                commandAst.InvocationOperator != TokenKind.Unknown &&
                context.WordToComplete != string.Empty)
            {
                // Use literal path after Ampersand
                var tryCmdletCompletion = false;
                var clearLiteralPathsKey = TurnOnLiteralPathOption(context);

                if (context.WordToComplete.Contains('-'))
                {
                    tryCmdletCompletion = true;
                }

                try
                {
                    var fileCompletionResults = new List<CompletionResult>(CompleteFilename(context));
                    if (tryCmdletCompletion)
                    {
                        // It's actually command name completion, other than argument completion
                        var cmdletCompletionResults = CompleteCommand(context);
                        if (cmdletCompletionResults != null && cmdletCompletionResults.Count > 0)
                        {
                            fileCompletionResults.AddRange(cmdletCompletionResults);
                        }
                    }

                    return fileCompletionResults;
                }
                finally
                {
                    if (clearLiteralPathsKey)
                        context.Options.Remove("LiteralPaths");
                }
            }

            if (expressionAst is StringConstantExpressionAst)
            {
                var pathAst = (StringConstantExpressionAst)expressionAst;
                // Handle static member completion: echo [int]::<tab>
                var shareMatch = Regex.Match(pathAst.Value, @"^(\[[\w\d\.]+\]::[\w\d\*]*)$");
                if (shareMatch.Success)
                {
                    int fakeReplacementIndex, fakeReplacementLength;
                    var input = shareMatch.Groups[1].Value;
                    var completionParameters = CommandCompletion.MapStringInputToParsedInput(input, input.Length);
                    var completionAnalysis = new CompletionAnalysis(completionParameters.Item1, completionParameters.Item2, completionParameters.Item3, context.Options);
                    var ret = completionAnalysis.GetResults(
                        context.Helper.CurrentPowerShell,
                        out fakeReplacementIndex,
                        out fakeReplacementLength);

                    if (ret != null && ret.Count > 0)
                    {
                        var prefix = TokenKind.LParen.Text() + input.Substring(0, fakeReplacementIndex);
                        foreach (CompletionResult entry in ret)
                        {
                            string completionText = prefix + entry.CompletionText;
                            if (entry.ResultType.Equals(CompletionResultType.Property))
                                completionText += TokenKind.RParen.Text();
                            result.Add(new CompletionResult(completionText, entry.ListItemText, entry.ResultType,
                                                            entry.ToolTip));
                        }

                        return result;
                    }
                }

                // Handle member completion with wildcard: echo $a.*<tab>
                if (pathAst.Value.Contains('*') && secondToLastMemberAst != null &&
                    secondToLastMemberAst.Extent.EndLineNumber == pathAst.Extent.StartLineNumber &&
                    secondToLastMemberAst.Extent.EndColumnNumber == pathAst.Extent.StartColumnNumber)
                {
                    var memberName = pathAst.Value.EndsWith('*')
                                         ? pathAst.Value
                                         : pathAst.Value + "*";
                    var targetExpr = secondToLastMemberAst.Expression;
                    if (IsSplattedVariable(targetExpr))
                    {
                        // It's splatted variable, and the member completion is not useful
                        return result;
                    }

                    var memberAst = secondToLastMemberAst.Member as StringConstantExpressionAst;
                    if (memberAst != null)
                    {
                        memberName = memberAst.Value + memberName;
                    }

                    CompleteMemberHelper(false, memberName, targetExpr, context, result);
                    if (result.Count > 0)
                    {
                        context.ReplacementIndex =
                            ((InternalScriptPosition)secondToLastMemberAst.Expression.Extent.EndScriptPosition).Offset + 1;
                        if (memberAst != null)
                            context.ReplacementLength += memberAst.Value.Length;
                        return result;
                    }
                }

                // Treat it as the file name completion
                // Handle this scenario: & 'c:\a b'\<tab>
                string fileName = pathAst.Value;
                if (commandAst.InvocationOperator != TokenKind.Unknown && fileName.IndexOfAny(Utils.Separators.Directory) == 0 &&
                    commandAst.CommandElements.Count == 2 && commandAst.CommandElements[0] is StringConstantExpressionAst &&
                    commandAst.CommandElements[0].Extent.EndLineNumber == expressionAst.Extent.StartLineNumber &&
                    commandAst.CommandElements[0].Extent.EndColumnNumber == expressionAst.Extent.StartColumnNumber)
                {
                    if (pseudoBinding != null)
                    {
                        // CommandElements[0] is resolved to a command
                        return result;
                    }
                    else
                    {
                        var constantAst = (StringConstantExpressionAst)commandAst.CommandElements[0];
                        fileName = constantAst.Value + fileName;
                        context.ReplacementIndex = ((InternalScriptPosition)constantAst.Extent.StartScriptPosition).Offset;
                        context.ReplacementLength += ((InternalScriptPosition)constantAst.Extent.EndScriptPosition).Offset - context.ReplacementIndex;
                        context.WordToComplete = fileName;
                        // commandAst.InvocationOperator != TokenKind.Unknown, so we should use literal path
                        var clearLiteralPathKey = TurnOnLiteralPathOption(context);

                        try
                        {
                            return new List<CompletionResult>(CompleteFilename(context));
                        }
                        finally
                        {
                            if (clearLiteralPathKey)
                                context.Options.Remove("LiteralPaths");
                        }
                    }
                }
            }

            // The default argument completion: file path completion, command name completion('WordToComplete' is not empty and contains a dash).
            // If the current argument completion has been process already, we don't go through the default argument completion anymore.
            if (!hasBeenProcessed)
            {
                var commandName = commandAst.GetCommandName();
                var customCompleter = GetCustomArgumentCompleter(
                    "NativeArgumentCompleters",
                    new[] { commandName, Path.GetFileName(commandName), Path.GetFileNameWithoutExtension(commandName) },
                    context);
                if (customCompleter != null)
                {
                    if (InvokeScriptArgumentCompleter(
                        customCompleter,
                        new object[] { context.WordToComplete, commandAst, context.CursorPosition.Offset },
                        result))
                    {
                        return result;
                    }
                }

                var clearLiteralPathKey = false;
                if (pseudoBinding == null)
                {
                    // the command could be a native command such as notepad.exe, we use literal path in this case
                    clearLiteralPathKey = TurnOnLiteralPathOption(context);
                }

                try
                {
                    result = new List<CompletionResult>(CompleteFilename(context));
                }
                finally
                {
                    if (clearLiteralPathKey)
                        context.Options.Remove("LiteralPaths");
                }

                if (context.WordToComplete != string.Empty && context.WordToComplete.Contains('-'))
                {
                    var commandResults = CompleteCommand(context);
                    if (commandResults != null)
                        result.AddRange(commandResults);
                }
            }

            return result;
        }

        internal static string ConcatenateStringPathArguments(CommandElementAst stringAst, string partialPath, CompletionContext completionContext)
        {
            var constantPathAst = stringAst as StringConstantExpressionAst;
            if (constantPathAst != null)
            {
                string quote = string.Empty;
                switch (constantPathAst.StringConstantType)
                {
                    case StringConstantType.SingleQuoted:
                        quote = "'";
                        break;
                    case StringConstantType.DoubleQuoted:
                        quote = "\"";
                        break;
                    default:
                        break;
                }

                return quote + constantPathAst.Value + partialPath + quote;
            }
            else
            {
                var expandablePathAst = stringAst as ExpandableStringExpressionAst;
                string fullPath = null;
                if (expandablePathAst != null &&
                    IsPathSafelyExpandable(expandableStringAst: expandablePathAst,
                                           extraText: partialPath,
                                           executionContext: completionContext.ExecutionContext,
                                           expandedString: out fullPath))
                {
                    return fullPath;
                }
            }

            return null;
        }

        /// <summary>
        /// Get the argument completion results when the pseudo binding was not successful.
        /// </summary>
        private static List<CompletionResult> GetArgumentCompletionResultsWithFailedPseudoBinding(
            CompletionContext context,
            ArgumentLocation argLocation,
            CommandAst commandAst)
        {
            List<CompletionResult> result = new List<CompletionResult>();

            PseudoBindingInfo bindingInfo = context.PseudoBindingInfo;
            if (argLocation.IsPositional)
            {
                CompletePositionalArgument(
                    bindingInfo.CommandName,
                    commandAst,
                    context,
                    result,
                    bindingInfo.UnboundParameters,
                    bindingInfo.DefaultParameterSetFlag,
                    uint.MaxValue,
                    argLocation.Position);
            }
            else
            {
                string paramName = argLocation.Argument.ParameterName;
                WildcardPattern pattern = WildcardPattern.Get(paramName + "*", WildcardOptions.IgnoreCase);
                foreach (MergedCompiledCommandParameter param in bindingInfo.UnboundParameters)
                {
                    if (pattern.IsMatch(param.Parameter.Name))
                    {
                        ProcessParameter(bindingInfo.CommandName, commandAst, context, result, param);
                        break;
                    }

                    bool isAliasMatch = false;
                    foreach (string alias in param.Parameter.Aliases)
                    {
                        if (pattern.IsMatch(alias))
                        {
                            isAliasMatch = true;
                            ProcessParameter(bindingInfo.CommandName, commandAst, context, result, param);
                            break;
                        }
                    }

                    if (isAliasMatch)
                        break;
                }
            }

            return result;
        }

        /// <summary>
        /// Get the argument completion results when the pseudo binding was successful.
        /// </summary>
        private static List<CompletionResult> GetArgumentCompletionResultsWithSuccessfulPseudoBinding(
            CompletionContext context,
            ArgumentLocation argLocation,
            CommandAst commandAst)
        {
            PseudoBindingInfo bindingInfo = context.PseudoBindingInfo;
            Diagnostics.Assert(bindingInfo.InfoType.Equals(PseudoBindingInfoType.PseudoBindingSucceed), "Caller needs to make sure the pseudo binding was successful");
            List<CompletionResult> result = new List<CompletionResult>();

            if (argLocation.IsPositional && argLocation.Argument == null)
            {
                AstPair lastPositionalArg;
                AstParameterArgumentPair targetPositionalArg =
                    FindTargetPositionalArgument(
                        bindingInfo.AllParsedArguments,
                        argLocation.Position,
                        out lastPositionalArg);

                if (targetPositionalArg != null)
                    argLocation.Argument = targetPositionalArg;
                else
                {
                    if (lastPositionalArg != null)
                    {
                        bool lastPositionalGetBound = false;
                        Collection<string> parameterNames = new Collection<string>();

                        foreach (KeyValuePair<string, AstParameterArgumentPair> entry in bindingInfo.BoundArguments)
                        {
                            // positional argument
                            if (!entry.Value.ParameterSpecified)
                            {
                                var arg = (AstPair)entry.Value;
                                if (arg.Argument.GetHashCode() == lastPositionalArg.Argument.GetHashCode())
                                {
                                    lastPositionalGetBound = true;
                                    break;
                                }
                            }
                            else if (entry.Value.ParameterArgumentType.Equals(AstParameterArgumentType.AstArray))
                            {
                                // check if the positional argument would be bound to a "ValueFromRemainingArgument" parameter
                                var arg = (AstArrayPair)entry.Value;
                                if (arg.Argument.Any(exp => exp.GetHashCode() == lastPositionalArg.Argument.GetHashCode()))
                                {
                                    parameterNames.Add(entry.Key);
                                }
                            }
                        }

                        if (parameterNames.Count > 0)
                        {
                            // parameter should be in BoundParameters
                            foreach (string param in parameterNames)
                            {
                                MergedCompiledCommandParameter parameter = bindingInfo.BoundParameters[param];
                                ProcessParameter(bindingInfo.CommandName, commandAst, context, result, parameter, bindingInfo.BoundArguments);
                            }

                            return result;
                        }
                        else if (!lastPositionalGetBound)
                        {
                            // last positional argument was not bound, then positional argument 'tab' wants to
                            // expand will not get bound either
                            return result;
                        }
                    }

                    CompletePositionalArgument(
                        bindingInfo.CommandName,
                        commandAst,
                        context,
                        result,
                        bindingInfo.UnboundParameters,
                        bindingInfo.DefaultParameterSetFlag,
                        bindingInfo.ValidParameterSetsFlags,
                        argLocation.Position,
                        bindingInfo.BoundArguments);

                    return result;
                }
            }

            if (argLocation.Argument != null)
            {
                Collection<string> parameterNames = new Collection<string>();
                foreach (KeyValuePair<string, AstParameterArgumentPair> entry in bindingInfo.BoundArguments)
                {
                    if (entry.Value.ParameterArgumentType.Equals(AstParameterArgumentType.PipeObject))
                        continue;

                    if (entry.Value.ParameterArgumentType.Equals(AstParameterArgumentType.AstArray) && !argLocation.Argument.ParameterSpecified)
                    {
                        var arrayArg = (AstArrayPair)entry.Value;
                        var target = (AstPair)argLocation.Argument;
                        if (arrayArg.Argument.Any(exp => exp.GetHashCode() == target.Argument.GetHashCode()))
                        {
                            parameterNames.Add(entry.Key);
                        }
                    }
                    else if (entry.Value.GetHashCode() == argLocation.Argument.GetHashCode())
                    {
                        parameterNames.Add(entry.Key);
                    }
                }

                if (parameterNames.Count > 0)
                {
                    // those parameters should be in BoundParameters
                    foreach (string param in parameterNames)
                    {
                        MergedCompiledCommandParameter parameter = bindingInfo.BoundParameters[param];
                        ProcessParameter(bindingInfo.CommandName, commandAst, context, result, parameter, bindingInfo.BoundArguments);
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Get the positional argument completion results based on the position it's in the command line.
        /// </summary>
        private static void CompletePositionalArgument(
            string commandName,
            CommandAst commandAst,
            CompletionContext context,
            List<CompletionResult> result,
            IEnumerable<MergedCompiledCommandParameter> parameters,
            uint defaultParameterSetFlag,
            uint validParameterSetFlags,
            int position,
            Dictionary<string, AstParameterArgumentPair> boundArguments = null)
        {
            bool isProcessedAsPositional = false;
            bool isDefaultParameterSetValid = defaultParameterSetFlag != 0 &&
                                              (defaultParameterSetFlag & validParameterSetFlags) != 0;
            MergedCompiledCommandParameter positionalParam = null;

            foreach (MergedCompiledCommandParameter param in parameters)
            {
                bool isInParameterSet = (param.Parameter.ParameterSetFlags & validParameterSetFlags) != 0 || param.Parameter.IsInAllSets;
                if (!isInParameterSet)
                    continue;

                var parameterSetDataCollection = param.Parameter.GetMatchingParameterSetData(validParameterSetFlags);
                foreach (ParameterSetSpecificMetadata parameterSetData in parameterSetDataCollection)
                {
                    // in the first pass, we skip the remaining argument ones
                    if (parameterSetData.ValueFromRemainingArguments)
                    {
                        continue;
                    }

                    // Check the position
                    int positionInParameterSet = parameterSetData.Position;

                    if (positionInParameterSet == int.MinValue || positionInParameterSet != position)
                    {
                        // The parameter is not positional, or its position is not what we want
                        continue;
                    }

                    if (isDefaultParameterSetValid)
                    {
                        if (parameterSetData.ParameterSetFlag == defaultParameterSetFlag)
                        {
                            ProcessParameter(commandName, commandAst, context, result, param, boundArguments);
                            isProcessedAsPositional = result.Count > 0;
                            break;
                        }
                        else
                        {
                            if (positionalParam == null)
                                positionalParam = param;
                        }
                    }
                    else
                    {
                        isProcessedAsPositional = true;
                        ProcessParameter(commandName, commandAst, context, result, param, boundArguments);
                        break;
                    }
                }

                if (isProcessedAsPositional)
                    break;
            }

            if (!isProcessedAsPositional && positionalParam != null)
            {
                isProcessedAsPositional = true;
                ProcessParameter(commandName, commandAst, context, result, positionalParam, boundArguments);
            }

            if (!isProcessedAsPositional)
            {
                foreach (MergedCompiledCommandParameter param in parameters)
                {
                    bool isInParameterSet = (param.Parameter.ParameterSetFlags & validParameterSetFlags) != 0 || param.Parameter.IsInAllSets;
                    if (!isInParameterSet)
                        continue;

                    var parameterSetDataCollection = param.Parameter.GetMatchingParameterSetData(validParameterSetFlags);
                    foreach (ParameterSetSpecificMetadata parameterSetData in parameterSetDataCollection)
                    {
                        // in the second pass, we check the remaining argument ones
                        if (parameterSetData.ValueFromRemainingArguments)
                        {
                            ProcessParameter(commandName, commandAst, context, result, param, boundArguments);
                            break;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Process a parameter to get the argument completion results.
        /// </summary>
        /// <remarks>
        /// If the argument completion falls into these pre-defined cases:
        ///   1. The matching parameter is of type Enum
        ///   2. The matching parameter is of type SwitchParameter
        ///   3. The matching parameter is declared with ValidateSetAttribute
        ///   4. Falls into the native command argument completion
        /// a null instance of CompletionResult is added to the end of the
        /// "result" list, to indicate that this particular argument completion
        /// has been processed already. If the "result" list is still empty, we
        /// will not go through the default argument completion steps anymore.
        /// </remarks>
        private static void ProcessParameter(
            string commandName,
            CommandAst commandAst,
            CompletionContext context,
            List<CompletionResult> result,
            MergedCompiledCommandParameter parameter,
            Dictionary<string, AstParameterArgumentPair> boundArguments = null)
        {
            CompletionResult fullMatch = null;
            Type parameterType = GetEffectiveParameterType(parameter.Parameter.Type);

            if (parameterType.IsArray)
            {
                parameterType = parameterType.GetElementType();
            }

            if (parameterType.IsEnum)
            {
                RemoveLastNullCompletionResult(result);

                string enumString = LanguagePrimitives.EnumSingleTypeConverter.EnumValues(parameterType);
                string separator = CultureInfo.CurrentUICulture.TextInfo.ListSeparator;
                string[] enumArray = enumString.Split(separator, StringSplitOptions.RemoveEmptyEntries);

                string wordToComplete = context.WordToComplete;
                string quote = HandleDoubleAndSingleQuote(ref wordToComplete);

                var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
                var enumList = new List<string>();

                foreach (string value in enumArray)
                {
                    if (wordToComplete.Equals(value, StringComparison.OrdinalIgnoreCase))
                    {
                        string completionText = quote == string.Empty ? value : quote + value + quote;
                        fullMatch = new CompletionResult(completionText, value, CompletionResultType.ParameterValue, value);
                        continue;
                    }

                    if (pattern.IsMatch(value))
                    {
                        enumList.Add(value);
                    }
                }

                if (fullMatch != null)
                {
                    result.Add(fullMatch);
                }

                enumList.Sort();
                result.AddRange(from entry in enumList
                                let completionText = quote == string.Empty ? entry : quote + entry + quote
                                select new CompletionResult(completionText, entry, CompletionResultType.ParameterValue, entry));

                result.Add(CompletionResult.Null);
                return;
            }

            if (parameterType.Equals(typeof(SwitchParameter)))
            {
                RemoveLastNullCompletionResult(result);

                if (context.WordToComplete == string.Empty || context.WordToComplete.Equals("$", StringComparison.Ordinal))
                {
                    result.Add(new CompletionResult("$true", "$true", CompletionResultType.ParameterValue, "$true"));
                    result.Add(new CompletionResult("$false", "$false", CompletionResultType.ParameterValue, "$false"));
                }

                result.Add(CompletionResult.Null);
                return;
            }

            foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes)
            {
                if (att is ValidateSetAttribute setAtt)
                {
                    RemoveLastNullCompletionResult(result);

                    string wordToComplete = context.WordToComplete;
                    string quote = HandleDoubleAndSingleQuote(ref wordToComplete);

                    var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
                    var setList = new List<string>();

                    foreach (string value in setAtt.ValidValues)
                    {
                        if (value == string.Empty) { continue; }

                        if (wordToComplete.Equals(value, StringComparison.OrdinalIgnoreCase))
                        {
                            string completionText = quote == string.Empty ? value : quote + value + quote;
                            fullMatch = new CompletionResult(completionText, value, CompletionResultType.ParameterValue, value);
                            continue;
                        }

                        if (pattern.IsMatch(value))
                        {
                            setList.Add(value);
                        }
                    }

                    if (fullMatch != null)
                    {
                        result.Add(fullMatch);
                    }

                    setList.Sort();
                    foreach (string entry in setList)
                    {
                        string realEntry = entry;
                        string completionText = entry;
                        if (quote == string.Empty)
                        {
                            if (CompletionRequiresQuotes(entry, false))
                            {
                                realEntry = CodeGeneration.EscapeSingleQuotedStringContent(entry);
                                completionText = "'" + realEntry + "'";
                            }
                        }
                        else
                        {
                            if (quote.Equals("'", StringComparison.OrdinalIgnoreCase))
                            {
                                realEntry = CodeGeneration.EscapeSingleQuotedStringContent(entry);
                            }

                            completionText = quote + realEntry + quote;
                        }

                        result.Add(new CompletionResult(completionText, entry, CompletionResultType.ParameterValue, entry));
                    }

                    result.Add(CompletionResult.Null);
                    return;
                }
            }

            NativeCommandArgumentCompletion(commandName, parameter.Parameter, result, commandAst, context, boundArguments);
        }

        private static IEnumerable<PSTypeName> NativeCommandArgumentCompletion_InferTypesOfArgument(
            Dictionary<string, AstParameterArgumentPair> boundArguments,
            CommandAst commandAst,
            CompletionContext context,
            string parameterName)
        {
            if (boundArguments == null)
            {
                yield break;
            }

            AstParameterArgumentPair astParameterArgumentPair;
            if (!boundArguments.TryGetValue(parameterName, out astParameterArgumentPair))
            {
                yield break;
            }

            Ast argumentAst = null;
            switch (astParameterArgumentPair.ParameterArgumentType)
            {
                case AstParameterArgumentType.AstPair:
                    {
                        AstPair astPair = (AstPair)astParameterArgumentPair;
                        argumentAst = astPair.Argument;
                    }

                    break;

                case AstParameterArgumentType.PipeObject:
                    {
                        var pipelineAst = commandAst.Parent as PipelineAst;
                        if (pipelineAst != null)
                        {
                            int i;
                            for (i = 0; i < pipelineAst.PipelineElements.Count; i++)
                            {
                                if (pipelineAst.PipelineElements[i] == commandAst)
                                    break;
                            }

                            if (i != 0)
                            {
                                argumentAst = pipelineAst.PipelineElements[i - 1];
                            }
                        }
                    }

                    break;

                default:
                    break;
            }

            if (argumentAst == null)
            {
                yield break;
            }

            ExpressionAst argumentExpressionAst = argumentAst as ExpressionAst;
            if (argumentExpressionAst == null)
            {
                CommandExpressionAst argumentCommandExpressionAst = argumentAst as CommandExpressionAst;
                if (argumentCommandExpressionAst != null)
                {
                    argumentExpressionAst = argumentCommandExpressionAst.Expression;
                }
            }

            object argumentValue;
            if (argumentExpressionAst != null && SafeExprEvaluator.TrySafeEval(argumentExpressionAst, context.ExecutionContext, out argumentValue))
            {
                if (argumentValue != null)
                {
                    IEnumerable enumerable = LanguagePrimitives.GetEnumerable(argumentValue) ??
                                             new object[] { argumentValue };
                    foreach (var element in enumerable)
                    {
                        if (element == null)
                        {
                            continue;
                        }

                        PSObject pso = PSObject.AsPSObject(element);
                        if ((pso.TypeNames.Count > 0) && (!(pso.TypeNames[0].Equals(pso.BaseObject.GetType().FullName, StringComparison.OrdinalIgnoreCase))))
                        {
                            yield return new PSTypeName(pso.TypeNames[0]);
                        }

                        if (pso.BaseObject is not PSCustomObject)
                        {
                            yield return new PSTypeName(pso.BaseObject.GetType());
                        }
                    }

                    yield break;
                }
            }

            foreach (PSTypeName typeName in AstTypeInference.InferTypeOf(argumentAst, context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval))
            {
                yield return typeName;
            }
        }

        internal static IList<string> NativeCommandArgumentCompletion_ExtractSecondaryArgument(
            Dictionary<string, AstParameterArgumentPair> boundArguments,
            string parameterName)
        {
            List<string> result = new List<string>();

            if (boundArguments == null)
            {
                return result;
            }

            AstParameterArgumentPair argumentValue;
            if (!boundArguments.TryGetValue(parameterName, out argumentValue))
            {
                return result;
            }

            switch (argumentValue.ParameterArgumentType)
            {
                case AstParameterArgumentType.AstPair:
                    {
                        var value = (AstPair)argumentValue;
                        if (value.Argument is StringConstantExpressionAst)
                        {
                            var argument = (StringConstantExpressionAst)value.Argument;
                            result.Add(argument.Value);
                        }
                        else if (value.Argument is ArrayLiteralAst)
                        {
                            var argument = (ArrayLiteralAst)value.Argument;
                            foreach (ExpressionAst entry in argument.Elements)
                            {
                                var entryAsString = entry as StringConstantExpressionAst;
                                if (entryAsString != null)
                                {
                                    result.Add(entryAsString.Value);
                                }
                                else
                                {
                                    result.Clear();
                                    break;
                                }
                            }
                        }

                        break;
                    }
                case AstParameterArgumentType.AstArray:
                    {
                        var value = (AstArrayPair)argumentValue;
                        var argument = value.Argument;

                        foreach (ExpressionAst entry in argument)
                        {
                            var entryAsString = entry as StringConstantExpressionAst;
                            if (entryAsString != null)
                            {
                                result.Add(entryAsString.Value);
                            }
                            else
                            {
                                result.Clear();
                                break;
                            }
                        }

                        break;
                    }
                default:
                    break;
            }

            return result;
        }

        private static void NativeCommandArgumentCompletion(
            string commandName,
            CompiledCommandParameter parameter,
            List<CompletionResult> result,
            CommandAst commandAst,
            CompletionContext context,
            Dictionary<string, AstParameterArgumentPair> boundArguments = null)
        {
            string parameterName = parameter.Name;

            // Fall back to the commandAst command name if a command name is not found. This can be caused by a script block or AST with the matching function definition being passed to CompleteInput
            // This allows for editors and other tools using CompleteInput with Script/AST definations to get values from RegisteredArgumentCompleters to better match the console experience.
            // See issue https://github.com/PowerShell/PowerShell/issues/10567
            string actualCommandName = string.IsNullOrEmpty(commandName)
                        ? commandAst.GetCommandName()
                        : commandName;

            if (string.IsNullOrEmpty(actualCommandName))
            {
                return;
            }

            string parameterFullName = $"{actualCommandName}:{parameterName}";

            ScriptBlock customCompleter = GetCustomArgumentCompleter(
                "CustomArgumentCompleters",
                new[] { parameterFullName, parameterName },
                context);

            if (customCompleter != null)
            {
                if (InvokeScriptArgumentCompleter(
                    customCompleter,
                    commandName, parameterName, context.WordToComplete, commandAst, context,
                    result))
                {
                    return;
                }
            }

            var argumentCompleterAttribute = parameter.CompiledAttributes.OfType<ArgumentCompleterAttribute>().FirstOrDefault();
            if (argumentCompleterAttribute != null)
            {
                try
                {
                    if (argumentCompleterAttribute.Type != null)
                    {
                        var completer = Activator.CreateInstance(argumentCompleterAttribute.Type) as IArgumentCompleter;
                        if (completer != null)
                        {
                            var customResults = completer.CompleteArgument(commandName, parameterName,
                                context.WordToComplete, commandAst, GetBoundArgumentsAsHashtable(context));
                            if (customResults != null)
                            {
                                result.AddRange(customResults);
                                result.Add(CompletionResult.Null);
                                return;
                            }
                        }
                    }
                    else
                    {
                        if (InvokeScriptArgumentCompleter(
                            argumentCompleterAttribute.ScriptBlock,
                            commandName, parameterName, context.WordToComplete, commandAst, context,
                            result))
                        {
                            return;
                        }
                    }
                }
                catch (Exception)
                {
                }
            }

            var argumentCompletionsAttribute = parameter.CompiledAttributes.OfType<ArgumentCompletionsAttribute>().FirstOrDefault();
            if (argumentCompletionsAttribute != null)
            {
                var customResults = argumentCompletionsAttribute.CompleteArgument(commandName, parameterName,
                        context.WordToComplete, commandAst, GetBoundArgumentsAsHashtable(context));
                if (customResults != null)
                {
                    result.AddRange(customResults);
                    result.Add(CompletionResult.Null);
                    return;
                }
            }

            switch (commandName)
            {
                case "Get-Command":
                    {
                        if (parameterName.Equals("Module", StringComparison.OrdinalIgnoreCase))
                        {
                            NativeCompletionGetCommand(context, /* moduleName: */ null, parameterName, result);
                            break;
                        }

                        if (parameterName.Equals("Name", StringComparison.OrdinalIgnoreCase))
                        {
                            var moduleNames = NativeCommandArgumentCompletion_ExtractSecondaryArgument(boundArguments, "Module");

                            if (moduleNames.Count > 0)
                            {
                                foreach (string module in moduleNames)
                                {
                                    NativeCompletionGetCommand(context, module, parameterName, result);
                                }
                            }
                            else
                            {
                                NativeCompletionGetCommand(context, /* moduleName: */ null, parameterName, result);
                            }

                            break;
                        }

                        if (parameterName.Equals("ParameterType", StringComparison.OrdinalIgnoreCase))
                        {
                            NativeCompletionTypeName(context, result);
                            break;
                        }

                        break;
                    }
                case "Show-Command":
                    {
                        NativeCompletionGetHelpCommand(context, parameterName, /* isHelpRelated: */ false, result);
                        break;
                    }
                case "help":
                case "Get-Help":
                    {
                        NativeCompletionGetHelpCommand(context, parameterName, /* isHelpRelated: */ true, result);
                        break;
                    }
                case "Invoke-Expression":
                    {
                        if (parameterName.Equals("Command", StringComparison.OrdinalIgnoreCase))
                        {
                            var commandResults = CompleteCommand(context);
                            if (commandResults != null)
                                result.AddRange(commandResults);
                        }

                        break;
                    }
                case "Clear-EventLog":
                case "Get-EventLog":
                case "Limit-EventLog":
                case "Remove-EventLog":
                case "Write-EventLog":
                    {
                        NativeCompletionEventLogCommands(context, parameterName, result);
                        break;
                    }
                case "Get-Job":
                case "Receive-Job":
                case "Remove-Job":
                case "Stop-Job":
                case "Wait-Job":
                case "Suspend-Job":
                case "Resume-Job":
                    {
                        NativeCompletionJobCommands(context, parameterName, result);
                        break;
                    }
                case "Disable-ScheduledJob":
                case "Enable-ScheduledJob":
                case "Get-ScheduledJob":
                case "Unregister-ScheduledJob":
                    {
                        NativeCompletionScheduledJobCommands(context, parameterName, result);
                        break;
                    }
                case "Get-Module":
                    {
                        bool loadedModulesOnly = boundArguments == null || !boundArguments.ContainsKey("ListAvailable");
                        bool skipEditionCheck = !loadedModulesOnly && boundArguments.ContainsKey("SkipEditionCheck");
                        NativeCompletionModuleCommands(context, parameterName, result, loadedModulesOnly, skipEditionCheck: skipEditionCheck);
                        break;
                    }
                case "Remove-Module":
                    {
                        NativeCompletionModuleCommands(context, parameterName, result, loadedModulesOnly: true);
                        break;
                    }
                case "Import-Module":
                    {
                        bool skipEditionCheck = boundArguments != null && boundArguments.ContainsKey("SkipEditionCheck");
                        NativeCompletionModuleCommands(context, parameterName, result, isImportModule: true, skipEditionCheck: skipEditionCheck);
                        break;
                    }
                case "Debug-Process":
                case "Get-Process":
                case "Stop-Process":
                case "Wait-Process":
                case "Enter-PSHostProcess":
                    {
                        NativeCompletionProcessCommands(context, parameterName, result);
                        break;
                    }
                case "Get-PSDrive":
                case "Remove-PSDrive":
                    {
                        if (parameterName.Equals("PSProvider", StringComparison.OrdinalIgnoreCase))
                        {
                            NativeCompletionProviderCommands(context, parameterName, result);
                        }
                        else if (parameterName.Equals("Name", StringComparison.OrdinalIgnoreCase))
                        {
                            var psProviders = NativeCommandArgumentCompletion_ExtractSecondaryArgument(boundArguments, "PSProvider");
                            if (psProviders.Count > 0)
                            {
                                foreach (string psProvider in psProviders)
                                {
                                    NativeCompletionDriveCommands(context, psProvider, parameterName, result);
                                }
                            }
                            else
                            {
                                NativeCompletionDriveCommands(context, /* psProvider: */ null, parameterName, result);
                            }
                        }

                        break;
                    }
                case "New-PSDrive":
                    {
                        NativeCompletionProviderCommands(context, parameterName, result);
                        break;
                    }
                case "Get-PSProvider":
                    {
                        NativeCompletionProviderCommands(context, parameterName, result);
                        break;
                    }
                case "Get-Service":
                case "Start-Service":
                case "Restart-Service":
                case "Resume-Service":
                case "Set-Service":
                case "Stop-Service":
                case "Suspend-Service":
                    {
                        NativeCompletionServiceCommands(context, parameterName, result);
                        break;
                    }
                case "Clear-Variable":
                case "Get-Variable":
                case "Remove-Variable":
                case "Set-Variable":
                    {
                        NativeCompletionVariableCommands(context, parameterName, result);
                        break;
                    }
                case "Get-Alias":
                    {
                        NativeCompletionAliasCommands(context, parameterName, result);
                        break;
                    }
                case "Get-TraceSource":
                case "Set-TraceSource":
                case "Trace-Command":
                    {
                        NativeCompletionTraceSourceCommands(context, parameterName, result);
                        break;
                    }
                case "Push-Location":
                case "Set-Location":
                    {
                        NativeCompletionSetLocationCommand(context, parameterName, result);
                        break;
                    }
                case "Move-Item":
                case "Copy-Item":
                    {
                        NativeCompletionCopyMoveItemCommand(context, parameterName, result);
                        break;
                    }
                case "New-Item":
                    {
                        NativeCompletionNewItemCommand(context, parameterName, result);
                        break;
                    }
                case "ForEach-Object":
                    {
                        if (parameterName.Equals("MemberName", StringComparison.OrdinalIgnoreCase))
                        {
                            NativeCompletionMemberName(context, result, commandAst);
                        }

                        break;
                    }
                case "Group-Object":
                case "Measure-Object":
                case "Sort-Object":
                case "Where-Object":
                case "Format-Custom":
                case "Format-List":
                case "Format-Table":
                case "Format-Wide":
                    {
                        if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase))
                        {
                            NativeCompletionMemberName(context, result, commandAst);
                        }

                        break;
                    }
                case "Select-Object":
                    {
                        if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase)
                         || parameterName.Equals("ExcludeProperty", StringComparison.OrdinalIgnoreCase)
                         || parameterName.Equals("ExpandProperty", StringComparison.OrdinalIgnoreCase))
                        {
                            NativeCompletionMemberName(context, result, commandAst);
                        }

                        break;
                    }

                case "New-Object":
                    {
                        if (parameterName.Equals("TypeName", StringComparison.OrdinalIgnoreCase))
                        {
                            NativeCompletionTypeName(context, result);
                        }

                        break;
                    }

                case "Get-CimClass":
                case "Get-CimInstance":
                case "Get-CimAssociatedInstance":
                case "Invoke-CimMethod":
                case "New-CimInstance":
                case "Register-CimIndicationEvent":
                    {
                        NativeCompletionCimCommands(parameterName, boundArguments, result, commandAst, context);
                        break;
                    }

                default:
                    {
                        NativeCompletionPathArgument(context, parameterName, result);
                        break;
                    }
            }
        }

        private static Hashtable GetBoundArgumentsAsHashtable(CompletionContext context)
        {
            var result = new Hashtable(StringComparer.OrdinalIgnoreCase);
            if (context.PseudoBindingInfo != null)
            {
                var boundArguments = context.PseudoBindingInfo.BoundArguments;
                if (boundArguments != null)
                {
                    foreach (var boundArgument in boundArguments)
                    {
                        var astPair = boundArgument.Value as AstPair;
                        if (astPair != null)
                        {
                            var parameterAst = astPair.Argument as CommandParameterAst;
                            var exprAst = parameterAst != null
                                              ? parameterAst.Argument
                                              : astPair.Argument as ExpressionAst;
                            object value;
                            if (exprAst != null && SafeExprEvaluator.TrySafeEval(exprAst, context.ExecutionContext, out value))
                            {
                                result[boundArgument.Key] = value;
                            }

                            continue;
                        }

                        var switchPair = boundArgument.Value as SwitchPair;
                        if (switchPair != null)
                        {
                            result[boundArgument.Key] = switchPair.Argument;
                            continue;
                        }
                        // Ignored:
                        //     AstArrayPair - only used for ValueFromRemainingArguments, not that useful for tab completion
                        //     FakePair - missing argument, not that useful
                        //     PipeObjectPair - no actual argument, makes for a poor api
                    }
                }
            }

            return result;
        }

        private static ScriptBlock GetCustomArgumentCompleter(
            string optionKey,
            IEnumerable<string> keys,
            CompletionContext context)
        {
            ScriptBlock scriptBlock;
            var options = context.Options;
            if (options != null)
            {
                var customCompleters = options[optionKey] as Hashtable;
                if (customCompleters != null)
                {
                    foreach (var key in keys)
                    {
                        if (customCompleters.ContainsKey(key))
                        {
                            scriptBlock = customCompleters[key] as ScriptBlock;
                            if (scriptBlock != null)
                                return scriptBlock;
                        }
                    }
                }
            }

            var registeredCompleters = optionKey.Equals("NativeArgumentCompleters", StringComparison.OrdinalIgnoreCase)
                ? context.NativeArgumentCompleters
                : context.CustomArgumentCompleters;

            if (registeredCompleters != null)
            {
                foreach (var key in keys)
                {
                    if (registeredCompleters.TryGetValue(key, out scriptBlock))
                    {
                        return scriptBlock;
                    }
                }
            }

            return null;
        }

        private static bool InvokeScriptArgumentCompleter(
            ScriptBlock scriptBlock,
            string commandName,
            string parameterName,
            string wordToComplete,
            CommandAst commandAst,
            CompletionContext context,
            List<CompletionResult> resultList)
        {
            bool result = InvokeScriptArgumentCompleter(
                scriptBlock,
                new object[] { commandName, parameterName, wordToComplete, commandAst, GetBoundArgumentsAsHashtable(context) },
                resultList);
            if (result)
            {
                resultList.Add(CompletionResult.Null);
            }

            return result;
        }

        private static bool InvokeScriptArgumentCompleter(
            ScriptBlock scriptBlock,
            object[] argumentsToCompleter,
            List<CompletionResult> result)
        {
            Collection<PSObject> customResults = null;
            try
            {
                customResults = scriptBlock.Invoke(argumentsToCompleter);
            }
            catch (Exception)
            {
            }

            if (customResults == null || customResults.Count == 0)
            {
                return false;
            }

            foreach (var customResult in customResults)
            {
                var resultAsCompletion = customResult.BaseObject as CompletionResult;
                if (resultAsCompletion != null)
                {
                    result.Add(resultAsCompletion);
                    continue;
                }

                var resultAsString = customResult.ToString();
                result.Add(new CompletionResult(resultAsString));
            }

            return true;
        }

        // All the methods for native command argument completion will add a null instance of the type CompletionResult to the end of the
        // "result" list, to indicate that this particular argument completion has fallen into one of the native command argument completion methods,
        // and has been processed already. So if the "result" list is still empty afterward, we will not go through the default argument completion anymore.
        #region Native Command Argument Completion

        private static void RemoveLastNullCompletionResult(List<CompletionResult> result)
        {
            if (result.Count > 0 && result[result.Count - 1].Equals(CompletionResult.Null))
            {
                result.RemoveAt(result.Count - 1);
            }
        }

        private static void NativeCompletionCimCommands(
            string parameter,
            Dictionary<string, AstParameterArgumentPair> boundArguments,
            List<CompletionResult> result,
            CommandAst commandAst,
            CompletionContext context)
        {
            if (boundArguments != null)
            {
                AstParameterArgumentPair astParameterArgumentPair;
                if ((boundArguments.TryGetValue("ComputerName", out astParameterArgumentPair)
                     || boundArguments.TryGetValue("CimSession", out astParameterArgumentPair))
                    && astParameterArgumentPair != null)
                {
                    switch (astParameterArgumentPair.ParameterArgumentType)
                    {
                        case AstParameterArgumentType.PipeObject:
                        case AstParameterArgumentType.Fake:
                            break;

                        default:
                            return; // we won't tab-complete remote class names
                    }
                }
            }

            if (parameter.Equals("Namespace", StringComparison.OrdinalIgnoreCase))
            {
                NativeCompletionCimNamespace(result, context);
                result.Add(CompletionResult.Null);
                return;
            }

            string pseudoboundCimNamespace = NativeCommandArgumentCompletion_ExtractSecondaryArgument(boundArguments, "Namespace").FirstOrDefault();
            if (parameter.Equals("ClassName", StringComparison.OrdinalIgnoreCase))
            {
                NativeCompletionCimClassName(pseudoboundCimNamespace, result, context);
                result.Add(CompletionResult.Null);
                return;
            }

            bool gotInstance = false;
            IEnumerable<PSTypeName> cimClassTypeNames = null;
            string pseudoboundClassName = NativeCommandArgumentCompletion_ExtractSecondaryArgument(boundArguments, "ClassName").FirstOrDefault();
            if (pseudoboundClassName != null)
            {
                gotInstance = false;
                var tmp = new List<PSTypeName>();
                tmp.Add(new PSTypeName(typeof(CimInstance).FullName + "#" + (pseudoboundCimNamespace ?? "root/cimv2") + "/" + pseudoboundClassName));
                cimClassTypeNames = tmp;
            }
            else if (boundArguments != null && boundArguments.ContainsKey("InputObject"))
            {
                gotInstance = true;
                cimClassTypeNames = NativeCommandArgumentCompletion_InferTypesOfArgument(boundArguments, commandAst, context, "InputObject");
            }

            if (cimClassTypeNames != null)
            {
                foreach (PSTypeName typeName in cimClassTypeNames)
                {
                    if (TypeInferenceContext.ParseCimCommandsTypeName(typeName, out pseudoboundCimNamespace, out pseudoboundClassName))
                    {
                        if (parameter.Equals("ResultClassName", StringComparison.OrdinalIgnoreCase))
                        {
                            NativeCompletionCimAssociationResultClassName(pseudoboundCimNamespace, pseudoboundClassName, result, context);
                        }
                        else if (parameter.Equals("MethodName", StringComparison.OrdinalIgnoreCase))
                        {
                            NativeCompletionCimMethodName(pseudoboundCimNamespace, pseudoboundClassName, !gotInstance, result, context);
                        }
                    }
                }

                result.Add(CompletionResult.Null);
            }
        }

        private static ConcurrentDictionary<string, IEnumerable<string>> s_cimNamespaceAndClassNameToAssociationResultClassNames =
            new ConcurrentDictionary<string, IEnumerable<string>>(StringComparer.OrdinalIgnoreCase);

        private static IEnumerable<string> NativeCompletionCimAssociationResultClassName_GetResultClassNames(
            string cimNamespaceOfSource,
            string cimClassNameOfSource)
        {
            StringBuilder safeClassName = new StringBuilder();
            foreach (char c in cimClassNameOfSource)
            {
                if (char.IsLetterOrDigit(c) || c == '_')
                {
                    safeClassName.Append(c);
                }
            }

            List<string> resultClassNames = new List<string>();
            using (var cimSession = CimSession.Create(null))
            {
                CimClass cimClass = cimSession.GetClass(cimNamespaceOfSource ?? "root/cimv2", cimClassNameOfSource);
                while (cimClass != null)
                {
                    string query = string.Format(
                        CultureInfo.InvariantCulture,
                        "associators of {{{0}}} WHERE SchemaOnly",
                        cimClass.CimSystemProperties.ClassName);

                    resultClassNames.AddRange(
                        cimSession.QueryInstances(cimNamespaceOfSource ?? "root/cimv2", "WQL", query)
                            .Select(associationInstance => associationInstance.CimSystemProperties.ClassName));

                    cimClass = cimClass.CimSuperClass;
                }
            }

            resultClassNames.Sort(StringComparer.OrdinalIgnoreCase);

            return resultClassNames.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
        }

        private static void NativeCompletionCimAssociationResultClassName(
            string pseudoboundNamespace,
            string pseudoboundClassName,
            List<CompletionResult> result,
            CompletionContext context)
        {
            if (string.IsNullOrWhiteSpace(pseudoboundClassName))
            {
                return;
            }

            IEnumerable<string> resultClassNames = s_cimNamespaceAndClassNameToAssociationResultClassNames.GetOrAdd(
                (pseudoboundNamespace ?? "root/cimv2") + ":" + pseudoboundClassName,
                _ => NativeCompletionCimAssociationResultClassName_GetResultClassNames(pseudoboundNamespace, pseudoboundClassName));

            WildcardPattern resultClassNamePattern = WildcardPattern.Get(context.WordToComplete + "*", WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);
            result.AddRange(resultClassNames
                .Where(resultClassNamePattern.IsMatch)
                .Select(x => new CompletionResult(x, x, CompletionResultType.Type, string.Format(CultureInfo.InvariantCulture, "{0} -> {1}", pseudoboundClassName, x))));
        }

        private static void NativeCompletionCimMethodName(
            string pseudoboundNamespace,
            string pseudoboundClassName,
            bool staticMethod,
            List<CompletionResult> result,
            CompletionContext context)
        {
            if (string.IsNullOrWhiteSpace(pseudoboundClassName))
            {
                return;
            }

            CimClass cimClass;
            using (var cimSession = CimSession.Create(null))
            {
                cimClass = cimSession.GetClass(pseudoboundNamespace ?? "root/cimv2", pseudoboundClassName);
            }

            WildcardPattern methodNamePattern = WildcardPattern.Get(context.WordToComplete + "*", WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase);
            List<CompletionResult> localResults = new List<CompletionResult>();
            foreach (CimMethodDeclaration methodDeclaration in cimClass.CimClassMethods)
            {
                string methodName = methodDeclaration.Name;
                if (!methodNamePattern.IsMatch(methodName))
                {
                    continue;
                }

                bool currentMethodIsStatic = methodDeclaration.Qualifiers.Any(q => q.Name.Equals("Static", StringComparison.OrdinalIgnoreCase));
                if ((currentMethodIsStatic && !staticMethod) || (!currentMethodIsStatic && staticMethod))
                {
                    continue;
                }

                StringBuilder tooltipText = new StringBuilder();
                tooltipText.Append(methodName);
                tooltipText.Append("(");
                bool gotFirstParameter = false;
                foreach (var methodParameter in methodDeclaration.Parameters)
                {
                    bool outParameter = methodParameter.Qualifiers.Any(q => q.Name.Equals("Out", StringComparison.OrdinalIgnoreCase));

                    if (!gotFirstParameter)
                    {
                        gotFirstParameter = true;
                    }
                    else
                    {
                        tooltipText.Append(", ");
                    }

                    if (outParameter)
                    {
                        tooltipText.Append("[out] ");
                    }

                    tooltipText.Append(CimInstanceAdapter.CimTypeToTypeNameDisplayString(methodParameter.CimType));
                    tooltipText.Append(" ");
                    tooltipText.Append(methodParameter.Name);

                    if (outParameter)
                    {
                        continue;
                    }
                }

                tooltipText.Append(")");

                localResults.Add(new CompletionResult(methodName, methodName, CompletionResultType.Method, tooltipText.ToString()));
            }

            result.AddRange(localResults.OrderBy(x => x.ListItemText, StringComparer.OrdinalIgnoreCase));
        }

        private static ConcurrentDictionary<string, IEnumerable<string>> s_cimNamespaceToClassNames =
            new ConcurrentDictionary<string, IEnumerable<string>>(StringComparer.OrdinalIgnoreCase);

        private static IEnumerable<string> NativeCompletionCimClassName_GetClassNames(string targetNamespace)
        {
            List<string> result = new List<string>();
            using (CimSession cimSession = CimSession.Create(null))
            {
                using (var operationOptions = new CimOperationOptions { ClassNamesOnly = true })
                    foreach (CimClass cimClass in cimSession.EnumerateClasses(targetNamespace, null, operationOptions))
                        using (cimClass)
                        {
                            string className = cimClass.CimSystemProperties.ClassName;
                            result.Add(className);
                        }
            }

            return result;
        }

        private static void NativeCompletionCimClassName(
            string pseudoBoundNamespace,
            List<CompletionResult> result,
            CompletionContext context)
        {
            string targetNamespace = pseudoBoundNamespace ?? "root/cimv2";

            List<string> regularClasses = new List<string>();
            List<string> systemClasses = new List<string>();

            IEnumerable<string> allClasses = s_cimNamespaceToClassNames.GetOrAdd(
                targetNamespace,
                NativeCompletionCimClassName_GetClassNames);
            WildcardPattern classNamePattern = WildcardPattern.Get(context.WordToComplete + "*", WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase);

            foreach (string className in allClasses)
            {
                if (context.Helper.CancelTabCompletion)
                {
                    break;
                }

                if (!classNamePattern.IsMatch(className))
                {
                    continue;
                }

                if (className.Length > 0 && className[0] == '_')
                {
                    systemClasses.Add(className);
                }
                else
                {
                    regularClasses.Add(className);
                }
            }

            regularClasses.Sort(StringComparer.OrdinalIgnoreCase);
            systemClasses.Sort(StringComparer.OrdinalIgnoreCase);
            result.AddRange(
                regularClasses.Concat(systemClasses)
                    .Select(className => new CompletionResult(className, className, CompletionResultType.Type, targetNamespace + ":" + className)));
        }

        private static void NativeCompletionCimNamespace(
            List<CompletionResult> result,
            CompletionContext context)
        {
            string containerNamespace = "root";
            string prefixOfChildNamespace = string.Empty;
            if (!string.IsNullOrEmpty(context.WordToComplete))
            {
                int lastSlashOrBackslash = context.WordToComplete.LastIndexOfAny(Utils.Separators.Directory);
                if (lastSlashOrBackslash != (-1))
                {
                    containerNamespace = context.WordToComplete.Substring(0, lastSlashOrBackslash);
                    prefixOfChildNamespace = context.WordToComplete.Substring(lastSlashOrBackslash + 1);
                }
            }

            List<CompletionResult> namespaceResults = new List<CompletionResult>();
            WildcardPattern childNamespacePattern = WildcardPattern.Get(prefixOfChildNamespace + "*", WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);
            using (CimSession cimSession = CimSession.Create(null))
            {
                foreach (CimInstance namespaceInstance in cimSession.EnumerateInstances(containerNamespace, "__Namespace"))
                    using (namespaceInstance)
                    {
                        if (context.Helper.CancelTabCompletion)
                        {
                            break;
                        }

                        CimProperty namespaceNameProperty = namespaceInstance.CimInstanceProperties["Name"];
                        if (namespaceNameProperty == null)
                        {
                            continue;
                        }

                        string childNamespace = namespaceNameProperty.Value as string;
                        if (childNamespace == null)
                        {
                            continue;
                        }

                        if (!childNamespacePattern.IsMatch(childNamespace))
                        {
                            continue;
                        }

                        namespaceResults.Add(new CompletionResult(
                                                 containerNamespace + "/" + childNamespace,
                                                 childNamespace,
                                                 CompletionResultType.Namespace,
                                                 containerNamespace + "/" + childNamespace));
                    }
            }

            result.AddRange(namespaceResults.OrderBy(x => x.ListItemText, StringComparer.OrdinalIgnoreCase));
        }

        private static void NativeCompletionGetCommand(CompletionContext context, string moduleName, string paramName, List<CompletionResult> result)
        {
            if (!string.IsNullOrEmpty(paramName) && paramName.Equals("Name", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                // Available commands
                var commandResults = CompleteCommand(context, moduleName);
                if (commandResults != null)
                    result.AddRange(commandResults);

                // Consider files only if the -Module parameter is not present
                if (moduleName == null)
                {
                    // ps1 files and directories. We only complete the files with .ps1 extension for Get-Command, because the -Syntax
                    // may only works on files with .ps1 extension
                    var ps1Extension = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { StringLiterals.PowerShellScriptFileExtension };
                    var moduleFilesResults = new List<CompletionResult>(CompleteFilename(context, /* containerOnly: */ false, ps1Extension));
                    if (moduleFilesResults.Count > 0)
                        result.AddRange(moduleFilesResults);
                }

                result.Add(CompletionResult.Null);
            }
            else if (!string.IsNullOrEmpty(paramName) && paramName.Equals("Module", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                var modules = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                var moduleResults = CompleteModuleName(context, loadedModulesOnly: true);
                if (moduleResults != null)
                {
                    foreach (CompletionResult moduleResult in moduleResults)
                    {
                        if (!modules.Contains(moduleResult.ToolTip))
                        {
                            modules.Add(moduleResult.ToolTip);
                            result.Add(moduleResult);
                        }
                    }
                }

                moduleResults = CompleteModuleName(context, loadedModulesOnly: false);
                if (moduleResults != null)
                {
                    foreach (CompletionResult moduleResult in moduleResults)
                    {
                        if (!modules.Contains(moduleResult.ToolTip))
                        {
                            modules.Add(moduleResult.ToolTip);
                            result.Add(moduleResult);
                        }
                    }
                }

                result.Add(CompletionResult.Null);
            }
        }

        private static void NativeCompletionGetHelpCommand(CompletionContext context, string paramName, bool isHelpRelated, List<CompletionResult> result)
        {
            if (!string.IsNullOrEmpty(paramName) && paramName.Equals("Name", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                // Available commands
                const CommandTypes commandTypes = CommandTypes.Cmdlet | CommandTypes.Function | CommandTypes.Alias | CommandTypes.ExternalScript | CommandTypes.Configuration;
                var commandResults = CompleteCommand(context, /* moduleName: */ null, commandTypes);
                if (commandResults != null)
                    result.AddRange(commandResults);

                // ps1 files and directories
                var ps1Extension = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { StringLiterals.PowerShellScriptFileExtension };
                var fileResults = new List<CompletionResult>(CompleteFilename(context, /* containerOnly: */ false, ps1Extension));
                if (fileResults.Count > 0)
                    result.AddRange(fileResults);

                if (isHelpRelated)
                {
                    // Available topics
                    var helpTopicResults = CompleteHelpTopics(context);
                    if (helpTopicResults != null)
                        result.AddRange(helpTopicResults);
                }

                result.Add(CompletionResult.Null);
            }
        }

        private static void NativeCompletionEventLogCommands(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (!string.IsNullOrEmpty(paramName) && paramName.Equals("LogName", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                var logName = context.WordToComplete ?? string.Empty;
                var quote = HandleDoubleAndSingleQuote(ref logName);

                if (!logName.EndsWith('*'))
                {
                    logName += "*";
                }

                var pattern = WildcardPattern.Get(logName, WildcardOptions.IgnoreCase);

                var powerShellExecutionHelper = context.Helper;
                var powershell = powerShellExecutionHelper.AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-EventLog").AddParameter("LogName", "*");

                Exception exceptionThrown;
                var psObjects = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);

                if (psObjects != null)
                {
                    foreach (dynamic eventLog in psObjects)
                    {
                        var completionText = eventLog.Log.ToString();
                        var listItemText = completionText;

                        if (CompletionRequiresQuotes(completionText, false))
                        {
                            var quoteInUse = quote == string.Empty ? "'" : quote;
                            if (quoteInUse == "'")
                                completionText = completionText.Replace("'", "''");
                            completionText = quoteInUse + completionText + quoteInUse;
                        }
                        else
                        {
                            completionText = quote + completionText + quote;
                        }

                        if (pattern.IsMatch(listItemText))
                        {
                            result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
                        }
                    }
                }

                result.Add(CompletionResult.Null);
            }
        }

        private static void NativeCompletionJobCommands(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName))
                return;

            var wordToComplete = context.WordToComplete ?? string.Empty;
            var quote = HandleDoubleAndSingleQuote(ref wordToComplete);

            if (!wordToComplete.EndsWith('*'))
            {
                wordToComplete += "*";
            }

            var pattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase);

            var paramIsName = paramName.Equals("Name", StringComparison.OrdinalIgnoreCase);
            var (parameterName, value) = paramIsName ? ("Name", wordToComplete) : ("IncludeChildJob", (object)true);
            var powerShellExecutionHelper = context.Helper;
            powerShellExecutionHelper.AddCommandWithPreferenceSetting("Get-Job", typeof(GetJobCommand)).AddParameter(parameterName, value);

            Exception exceptionThrown;
            var psObjects = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
            if (psObjects == null)
                return;

            if (paramName.Equals("Id", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                foreach (dynamic psJob in psObjects)
                {
                    var completionText = psJob.Id.ToString();
                    if (pattern.IsMatch(completionText))
                    {
                        var listItemText = completionText;
                        completionText = quote + completionText + quote;
                        result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
                    }
                }

                result.Add(CompletionResult.Null);
            }
            else if (paramName.Equals("InstanceId", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                foreach (dynamic psJob in psObjects)
                {
                    var completionText = psJob.InstanceId.ToString();
                    if (pattern.IsMatch(completionText))
                    {
                        var listItemText = completionText;
                        completionText = quote + completionText + quote;
                        result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
                    }
                }

                result.Add(CompletionResult.Null);
            }
            else if (paramIsName)
            {
                RemoveLastNullCompletionResult(result);

                foreach (dynamic psJob in psObjects)
                {
                    var completionText = psJob.Name;
                    var listItemText = completionText;

                    if (CompletionRequiresQuotes(completionText, false))
                    {
                        var quoteInUse = quote == string.Empty ? "'" : quote;
                        if (quoteInUse == "'")
                            completionText = completionText.Replace("'", "''");
                        completionText = quoteInUse + completionText + quoteInUse;
                    }
                    else
                    {
                        completionText = quote + completionText + quote;
                    }

                    result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
                }

                result.Add(CompletionResult.Null);
            }
        }

        private static void NativeCompletionScheduledJobCommands(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName))
                return;

            var wordToComplete = context.WordToComplete ?? string.Empty;
            var quote = HandleDoubleAndSingleQuote(ref wordToComplete);

            if (!wordToComplete.EndsWith('*'))
            {
                wordToComplete += "*";
            }

            var pattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase);

            var powerShellExecutionHelper = context.Helper;
            if (paramName.Equals("Name", StringComparison.OrdinalIgnoreCase))
            {
                powerShellExecutionHelper.AddCommandWithPreferenceSetting("PSScheduledJob\\Get-ScheduledJob").AddParameter("Name", wordToComplete);
            }
            else
            {
                powerShellExecutionHelper.AddCommandWithPreferenceSetting("PSScheduledJob\\Get-ScheduledJob");
            }

            Exception exceptionThrown;
            var psObjects = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
            if (psObjects == null)
                return;

            if (paramName.Equals("Id", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                foreach (dynamic psJob in psObjects)
                {
                    var completionText = psJob.Id.ToString();
                    if (pattern.IsMatch(completionText))
                    {
                        var listItemText = completionText;
                        completionText = quote + completionText + quote;
                        result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
                    }
                }

                result.Add(CompletionResult.Null);
            }
            else if (paramName.Equals("Name", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                foreach (dynamic psJob in psObjects)
                {
                    var completionText = psJob.Name;
                    var listItemText = completionText;

                    if (CompletionRequiresQuotes(completionText, false))
                    {
                        var quoteInUse = quote == string.Empty ? "'" : quote;
                        if (quoteInUse == "'")
                            completionText = completionText.Replace("'", "''");
                        completionText = quoteInUse + completionText + quoteInUse;
                    }
                    else
                    {
                        completionText = quote + completionText + quote;
                    }

                    result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
                }

                result.Add(CompletionResult.Null);
            }
        }

        private static void NativeCompletionModuleCommands(
            CompletionContext context,
            string paramName,
            List<CompletionResult> result,
            bool loadedModulesOnly = false,
            bool isImportModule = false,
            bool skipEditionCheck = false)
        {
            if (string.IsNullOrEmpty(paramName))
            {
                return;
            }

            if (paramName.Equals("Name", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                if (isImportModule)
                {
                    var moduleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
                            {   StringLiterals.PowerShellScriptFileExtension,
                                StringLiterals.PowerShellModuleFileExtension,
                                StringLiterals.PowerShellDataFileExtension,
                                StringLiterals.PowerShellNgenAssemblyExtension,
                                StringLiterals.PowerShellILAssemblyExtension,
                                StringLiterals.PowerShellILExecutableExtension,
                                StringLiterals.PowerShellCmdletizationFileExtension
                            };
                    var moduleFilesResults = new List<CompletionResult>(CompleteFilename(context, containerOnly: false, moduleExtensions));
                    if (moduleFilesResults.Count > 0)
                        result.AddRange(moduleFilesResults);

                    var assemblyOrModuleName = context.WordToComplete;
                    if (assemblyOrModuleName.IndexOfAny(Utils.Separators.DirectoryOrDrive) != -1)
                    {
                        // The partial input is a path, then we don't iterate modules under $ENV:PSModulePath
                        return;
                    }
                }

                var moduleResults = CompleteModuleName(context, loadedModulesOnly, skipEditionCheck);
                if (moduleResults != null && moduleResults.Count > 0)
                    result.AddRange(moduleResults);

                result.Add(CompletionResult.Null);
            }
            else if (paramName.Equals("Assembly", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                var moduleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".dll" };
                var moduleFilesResults = new List<CompletionResult>(CompleteFilename(context, /* containerOnly: */ false, moduleExtensions));
                if (moduleFilesResults.Count > 0)
                    result.AddRange(moduleFilesResults);

                result.Add(CompletionResult.Null);
            }
        }

        private static void NativeCompletionProcessCommands(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName))
                return;

            var wordToComplete = context.WordToComplete ?? string.Empty;
            var quote = HandleDoubleAndSingleQuote(ref wordToComplete);

            if (!wordToComplete.EndsWith('*'))
            {
                wordToComplete += "*";
            }

            var powerShellExecutionHelper = context.Helper;
            if (paramName.Equals("Id", StringComparison.OrdinalIgnoreCase))
            {
                powerShellExecutionHelper.AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Process");
            }
            else
            {
                powerShellExecutionHelper.AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Process").AddParameter("Name", wordToComplete);
            }

            Exception exceptionThrown;
            var psObjects = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
            if (psObjects == null)
                return;

            if (paramName.Equals("Id", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                var pattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase);
                foreach (dynamic process in psObjects)
                {
                    var processId = process.Id.ToString();
                    if (pattern.IsMatch(processId))
                    {
                        var processName = process.Name;

                        var idAndName = $"{processId} - {processName}";
                        processId = quote + processId + quote;
                        result.Add(new CompletionResult(processId, idAndName, CompletionResultType.ParameterValue, idAndName));
                    }
                }

                result.Add(CompletionResult.Null);
            }
            else if (paramName.Equals("Name", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                var uniqueSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                foreach (dynamic process in psObjects)
                {
                    var completionText = process.Name;
                    var listItemText = completionText;

                    if (uniqueSet.Contains(completionText))
                        continue;

                    uniqueSet.Add(completionText);
                    if (CompletionRequiresQuotes(completionText, false))
                    {
                        var quoteInUse = quote == string.Empty ? "'" : quote;
                        if (quoteInUse == "'")
                            completionText = completionText.Replace("'", "''");
                        completionText = quoteInUse + completionText + quoteInUse;
                    }
                    else
                    {
                        completionText = quote + completionText + quote;
                    }

                    // on macOS, system processes names will be empty if PowerShell isn't run as `sudo`
                    if (string.IsNullOrEmpty(listItemText))
                    {
                        continue;
                    }

                    result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
                }

                result.Add(CompletionResult.Null);
            }
        }

        private static void NativeCompletionProviderCommands(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName) || !paramName.Equals("PSProvider", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            RemoveLastNullCompletionResult(result);

            var providerName = context.WordToComplete ?? string.Empty;
            var quote = HandleDoubleAndSingleQuote(ref providerName);

            if (!providerName.EndsWith('*'))
            {
                providerName += "*";
            }

            var powerShellExecutionHelper = context.Helper;
            powerShellExecutionHelper.AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-PSProvider").AddParameter("PSProvider", providerName);
            var psObjects = powerShellExecutionHelper.ExecuteCurrentPowerShell(out _);
            if (psObjects == null)
                return;

            foreach (dynamic providerInfo in psObjects)
            {
                var completionText = providerInfo.Name;
                var listItemText = completionText;

                if (CompletionRequiresQuotes(completionText, false))
                {
                    var quoteInUse = quote == string.Empty ? "'" : quote;
                    if (quoteInUse == "'")
                        completionText = completionText.Replace("'", "''");
                    completionText = quoteInUse + completionText + quoteInUse;
                }
                else
                {
                    completionText = quote + completionText + quote;
                }

                result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
            }

            result.Add(CompletionResult.Null);
        }

        private static void NativeCompletionDriveCommands(CompletionContext context, string psProvider, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName) || !paramName.Equals("Name", StringComparison.OrdinalIgnoreCase))
                return;

            RemoveLastNullCompletionResult(result);

            var wordToComplete = context.WordToComplete ?? string.Empty;
            var quote = HandleDoubleAndSingleQuote(ref wordToComplete);

            if (!wordToComplete.EndsWith('*'))
            {
                wordToComplete += "*";
            }

            var powerShellExecutionHelper = context.Helper;
            var powershell = powerShellExecutionHelper
                .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-PSDrive")
                .AddParameter("Name", wordToComplete);
            if (psProvider != null)
                powershell.AddParameter("PSProvider", psProvider);

            var psObjects = powerShellExecutionHelper.ExecuteCurrentPowerShell(out _);
            if (psObjects != null)
            {
                foreach (dynamic driveInfo in psObjects)
                {
                    var completionText = driveInfo.Name;
                    var listItemText = completionText;

                    if (CompletionRequiresQuotes(completionText, false))
                    {
                        var quoteInUse = quote == string.Empty ? "'" : quote;
                        if (quoteInUse == "'")
                            completionText = completionText.Replace("'", "''");
                        completionText = quoteInUse + completionText + quoteInUse;
                    }
                    else
                    {
                        completionText = quote + completionText + quote;
                    }

                    result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
                }
            }

            result.Add(CompletionResult.Null);
        }

        private static void NativeCompletionServiceCommands(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName))
                return;

            var wordToComplete = context.WordToComplete ?? string.Empty;
            var quote = HandleDoubleAndSingleQuote(ref wordToComplete);

            if (!wordToComplete.EndsWith('*'))
            {
                wordToComplete += "*";
            }

            Exception exceptionThrown;
            var powerShellExecutionHelper = context.Helper;
            if (paramName.Equals("DisplayName", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                powerShellExecutionHelper
                    .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Service")
                    .AddParameter("DisplayName", wordToComplete)
                    .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object")
                    .AddParameter("Property", "DisplayName");
                var psObjects = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
                if (psObjects != null)
                {
                    foreach (dynamic serviceInfo in psObjects)
                    {
                        var completionText = serviceInfo.DisplayName;
                        var listItemText = completionText;

                        if (CompletionRequiresQuotes(completionText, false))
                        {
                            var quoteInUse = quote == string.Empty ? "'" : quote;
                            if (quoteInUse == "'")
                                completionText = completionText.Replace("'", "''");
                            completionText = quoteInUse + completionText + quoteInUse;
                        }
                        else
                        {
                            completionText = quote + completionText + quote;
                        }

                        result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
                    }
                }

                result.Add(CompletionResult.Null);
            }
            else if (paramName.Equals("Name", StringComparison.OrdinalIgnoreCase))
            {
                RemoveLastNullCompletionResult(result);

                powerShellExecutionHelper.AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Service").AddParameter("Name", wordToComplete);
                var psObjects = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
                if (psObjects != null)
                {
                    foreach (dynamic serviceInfo in psObjects)
                    {
                        var completionText = serviceInfo.Name;
                        var listItemText = completionText;

                        if (CompletionRequiresQuotes(completionText, false))
                        {
                            var quoteInUse = quote == string.Empty ? "'" : quote;
                            if (quoteInUse == "'")
                                completionText = completionText.Replace("'", "''");
                            completionText = quoteInUse + completionText + quoteInUse;
                        }
                        else
                        {
                            completionText = quote + completionText + quote;
                        }

                        result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
                    }
                }

                result.Add(CompletionResult.Null);
            }
        }

        private static void NativeCompletionVariableCommands(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName) || !paramName.Equals("Name", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            RemoveLastNullCompletionResult(result);

            var variableName = context.WordToComplete ?? string.Empty;
            var quote = HandleDoubleAndSingleQuote(ref variableName);
            if (!variableName.EndsWith('*'))
            {
                variableName += "*";
            }

            var powerShellExecutionHelper = context.Helper;
            var powershell = powerShellExecutionHelper.AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Get-Variable").AddParameter("Name", variableName);
            var psObjects = powerShellExecutionHelper.ExecuteCurrentPowerShell(out _);
            if (psObjects == null)
                return;

            foreach (dynamic variable in psObjects)
            {
                var effectiveQuote = quote;
                var completionText = variable.Name;
                var listItemText = completionText;

                // Handle special characters ? and * in variable names
                if (completionText.IndexOfAny(Utils.Separators.StarOrQuestion) != -1)
                {
                    effectiveQuote = "'";
                    completionText = completionText.Replace("?", "`?");
                    completionText = completionText.Replace("*", "`*");
                }

                if (!completionText.Equals("$", StringComparison.Ordinal) && CompletionRequiresQuotes(completionText, false))
                {
                    var quoteInUse = effectiveQuote == string.Empty ? "'" : effectiveQuote;
                    if (quoteInUse == "'")
                        completionText = completionText.Replace("'", "''");
                    completionText = quoteInUse + completionText + quoteInUse;
                }
                else
                {
                    completionText = effectiveQuote + completionText + effectiveQuote;
                }

                result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
            }

            result.Add(CompletionResult.Null);
        }

        private static void NativeCompletionAliasCommands(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName) ||
                (!paramName.Equals("Definition", StringComparison.OrdinalIgnoreCase) &&
                 !paramName.Equals("Name", StringComparison.OrdinalIgnoreCase)))
            {
                return;
            }

            RemoveLastNullCompletionResult(result);

            var powerShellExecutionHelper = context.Helper;
            if (paramName.Equals("Name", StringComparison.OrdinalIgnoreCase))
            {
                var commandName = context.WordToComplete ?? string.Empty;
                var quote = HandleDoubleAndSingleQuote(ref commandName);

                if (!commandName.EndsWith('*'))
                {
                    commandName += "*";
                }

                Exception exceptionThrown;
                var powershell = powerShellExecutionHelper.AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Get-Alias").AddParameter("Name", commandName);
                var psObjects = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
                if (psObjects != null)
                {
                    foreach (dynamic aliasInfo in psObjects)
                    {
                        var completionText = aliasInfo.Name;
                        var listItemText = completionText;

                        if (CompletionRequiresQuotes(completionText, false))
                        {
                            var quoteInUse = quote == string.Empty ? "'" : quote;
                            if (quoteInUse == "'")
                                completionText = completionText.Replace("'", "''");
                            completionText = quoteInUse + completionText + quoteInUse;
                        }
                        else
                        {
                            completionText = quote + completionText + quote;
                        }

                        result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
                    }
                }
            }
            else
            {
                // Complete for the parameter Definition
                // Available commands
                const CommandTypes commandTypes = CommandTypes.Cmdlet | CommandTypes.Function | CommandTypes.ExternalScript | CommandTypes.Configuration;
                var commandResults = CompleteCommand(context, /* moduleName: */ null, commandTypes);
                if (commandResults != null && commandResults.Count > 0)
                    result.AddRange(commandResults);

                // The parameter Definition takes a file
                var fileResults = new List<CompletionResult>(CompleteFilename(context));
                if (fileResults.Count > 0)
                    result.AddRange(fileResults);
            }

            result.Add(CompletionResult.Null);
        }

        private static void NativeCompletionTraceSourceCommands(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName) || !paramName.Equals("Name", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            RemoveLastNullCompletionResult(result);

            var traceSourceName = context.WordToComplete ?? string.Empty;
            var quote = HandleDoubleAndSingleQuote(ref traceSourceName);

            if (!traceSourceName.EndsWith('*'))
            {
                traceSourceName += "*";
            }

            var powerShellExecutionHelper = context.Helper;
            var powershell = powerShellExecutionHelper.AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Get-TraceSource").AddParameter("Name", traceSourceName);
            Exception exceptionThrown;
            var psObjects = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
            if (psObjects == null)
                return;

            foreach (dynamic trace in psObjects)
            {
                var completionText = trace.Name;
                var listItemText = completionText;

                if (CompletionRequiresQuotes(completionText, false))
                {
                    var quoteInUse = quote == string.Empty ? "'" : quote;
                    if (quoteInUse == "'")
                        completionText = completionText.Replace("'", "''");
                    completionText = quoteInUse + completionText + quoteInUse;
                }
                else
                {
                    completionText = quote + completionText + quote;
                }

                result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText));
            }

            result.Add(CompletionResult.Null);
        }

        private static void NativeCompletionSetLocationCommand(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName) ||
                (!paramName.Equals("Path", StringComparison.OrdinalIgnoreCase) &&
                 !paramName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase)))
            {
                return;
            }

            RemoveLastNullCompletionResult(result);

            context.WordToComplete = context.WordToComplete ?? string.Empty;
            var clearLiteralPath = false;
            if (paramName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase))
            {
                clearLiteralPath = TurnOnLiteralPathOption(context);
            }

            try
            {
                var fileNameResults = CompleteFilename(context, containerOnly: true, extension: null);
                if (fileNameResults != null)
                    result.AddRange(fileNameResults);
            }
            finally
            {
                if (clearLiteralPath)
                    context.Options.Remove("LiteralPaths");
            }

            result.Add(CompletionResult.Null);
        }

        /// <summary>
        /// Provides completion results for NewItemCommand.
        /// </summary>
        /// <param name="context">Completion context.</param>
        /// <param name="paramName">Name of the parameter whose value needs completion.</param>
        /// <param name="result">List of completion suggestions.</param>
        private static void NativeCompletionNewItemCommand(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName))
            {
                return;
            }

            var executionContext = context.ExecutionContext;

            var boundArgs = GetBoundArgumentsAsHashtable(context);
            var providedPath = boundArgs["Path"] as string ?? executionContext.SessionState.Path.CurrentLocation.Path;

            ProviderInfo provider;
            executionContext.LocationGlobber.GetProviderPath(providedPath, out provider);

            var isFileSystem = provider != null &&
                               provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase);

            // AutoComplete only if filesystem provider.
            if (isFileSystem)
            {
                if (paramName.Equals("ItemType", StringComparison.OrdinalIgnoreCase))
                {
                    if (!string.IsNullOrEmpty(context.WordToComplete))
                    {
                        WildcardPattern patternEvaluator = WildcardPattern.Get(context.WordToComplete + "*", WildcardOptions.IgnoreCase);

                        if (patternEvaluator.IsMatch("file"))
                        {
                            result.Add(new CompletionResult("File"));
                        }
                        else if (patternEvaluator.IsMatch("directory"))
                        {
                            result.Add(new CompletionResult("Directory"));
                        }
                        else if (patternEvaluator.IsMatch("symboliclink"))
                        {
                            result.Add(new CompletionResult("SymbolicLink"));
                        }
                        else if (patternEvaluator.IsMatch("junction"))
                        {
                            result.Add(new CompletionResult("Junction"));
                        }
                        else if (patternEvaluator.IsMatch("hardlink"))
                        {
                            result.Add(new CompletionResult("HardLink"));
                        }
                    }
                    else
                    {
                        result.Add(new CompletionResult("File"));
                        result.Add(new CompletionResult("Directory"));
                        result.Add(new CompletionResult("SymbolicLink"));
                        result.Add(new CompletionResult("Junction"));
                        result.Add(new CompletionResult("HardLink"));
                    }

                    result.Add(CompletionResult.Null);
                }
            }
        }

        private static void NativeCompletionCopyMoveItemCommand(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName))
            {
                return;
            }

            if (paramName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase) || paramName.Equals("Path", StringComparison.OrdinalIgnoreCase))
            {
                NativeCompletionPathArgument(context, paramName, result);
            }
            else if (paramName.Equals("Destination", StringComparison.OrdinalIgnoreCase))
            {
                // The parameter Destination for Move-Item and Copy-Item takes literal path
                RemoveLastNullCompletionResult(result);

                context.WordToComplete = context.WordToComplete ?? string.Empty;
                var clearLiteralPath = TurnOnLiteralPathOption(context);

                try
                {
                    var fileNameResults = CompleteFilename(context);
                    if (fileNameResults != null)
                        result.AddRange(fileNameResults);
                }
                finally
                {
                    if (clearLiteralPath)
                        context.Options.Remove("LiteralPaths");
                }

                result.Add(CompletionResult.Null);
            }
        }

        private static void NativeCompletionPathArgument(CompletionContext context, string paramName, List<CompletionResult> result)
        {
            if (string.IsNullOrEmpty(paramName) ||
                (!paramName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase) &&
                (!paramName.Equals("Path", StringComparison.OrdinalIgnoreCase)) &&
                (!paramName.Equals("FilePath", StringComparison.OrdinalIgnoreCase))))
            {
                return;
            }

            RemoveLastNullCompletionResult(result);

            context.WordToComplete = context.WordToComplete ?? string.Empty;
            var clearLiteralPath = false;
            if (paramName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase))
            {
                clearLiteralPath = TurnOnLiteralPathOption(context);
            }

            try
            {
                var fileNameResults = CompleteFilename(context);
                if (fileNameResults != null)
                    result.AddRange(fileNameResults);
            }
            finally
            {
                if (clearLiteralPath)
                    context.Options.Remove("LiteralPaths");
            }

            result.Add(CompletionResult.Null);
        }

        private static void NativeCompletionMemberName(CompletionContext context, List<CompletionResult> result, CommandAst commandAst)
        {
            // Command is something like where-object/foreach-object/format-list/etc. where there is a parameter that is a property name
            // and we want member names based on the input object, which is either the parameter InputObject, or comes from the pipeline.
            var pipelineAst = commandAst.Parent as PipelineAst;
            if (pipelineAst == null)
                return;

            int i;
            for (i = 0; i < pipelineAst.PipelineElements.Count; i++)
            {
                if (pipelineAst.PipelineElements[i] == commandAst)
                    break;
            }

            IEnumerable<PSTypeName> prevType = null;
            if (i == 0)
            {
                AstParameterArgumentPair pair;
                if (!context.PseudoBindingInfo.BoundArguments.TryGetValue("InputObject", out pair)
                    || !pair.ArgumentSpecified)
                {
                    return;
                }

                var astPair = pair as AstPair;
                if (astPair == null || astPair.Argument == null)
                {
                    return;
                }

                prevType = AstTypeInference.InferTypeOf(astPair.Argument, context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval);
            }
            else
            {
                prevType = AstTypeInference.InferTypeOf(pipelineAst.PipelineElements[i - 1], context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval);
            }

            CompleteMemberByInferredType(context.TypeInferenceContext, prevType, result, context.WordToComplete + "*", filter: IsPropertyMember, isStatic: false);
            result.Add(CompletionResult.Null);
        }

        private static void NativeCompletionTypeName(CompletionContext context, List<CompletionResult> result)
        {
            var wordToComplete = context.WordToComplete;
            var isQuoted = wordToComplete.Length > 0 && (wordToComplete[0].IsSingleQuote() || wordToComplete[0].IsDoubleQuote());
            string prefix = string.Empty;
            string suffix = string.Empty;
            if (isQuoted)
            {
                prefix = suffix = wordToComplete.Substring(0, 1);

                var endQuoted = (wordToComplete.Length > 1) && wordToComplete[wordToComplete.Length - 1] == wordToComplete[0];
                wordToComplete = wordToComplete.Substring(1, wordToComplete.Length - (endQuoted ? 2 : 1));
            }

            if (wordToComplete.Contains('['))
            {
                var cursor = (InternalScriptPosition)context.CursorPosition;
                cursor = cursor.CloneWithNewOffset(cursor.Offset - context.TokenAtCursor.Extent.StartOffset - (isQuoted ? 1 : 0));
                var fullTypeName = Parser.ScanType(wordToComplete, ignoreErrors: true);
                var typeNameToComplete = CompletionAnalysis.FindTypeNameToComplete(fullTypeName, cursor);
                if (typeNameToComplete == null)
                    return;

                var openBrackets = 0;
                var closeBrackets = 0;
                foreach (char c in wordToComplete)
                {
                    if (c == '[') openBrackets += 1;
                    else if (c == ']') closeBrackets += 1;
                }

                wordToComplete = typeNameToComplete.FullName;
                var typeNameText = fullTypeName.Extent.Text;
                if (!isQuoted)
                {
                    // We need to add quotes - the square bracket messes up parsing the argument
                    prefix = suffix = "'";
                }

                if (closeBrackets < openBrackets)
                {
                    suffix = suffix.Insert(0, new string(']', (openBrackets - closeBrackets)));
                }

                if (isQuoted && closeBrackets == openBrackets)
                {
                    // Already quoted, and has matching [].  We can give a better Intellisense experience
                    // if we only replace the minimum.
                    context.ReplacementIndex = typeNameToComplete.Extent.StartOffset + context.TokenAtCursor.Extent.StartOffset + 1;
                    context.ReplacementLength = wordToComplete.Length;
                    prefix = suffix = string.Empty;
                }
                else
                {
                    prefix += typeNameText.Substring(0, typeNameToComplete.Extent.StartOffset);
                    suffix = suffix.Insert(0, typeNameText.Substring(typeNameToComplete.Extent.EndOffset));
                }
            }

            context.WordToComplete = wordToComplete;

            var typeResults = CompleteType(context, prefix, suffix);
            if (typeResults != null)
            {
                result.AddRange(typeResults);
            }

            result.Add(CompletionResult.Null);
        }

        #endregion Native Command Argument Completion

        /// <summary>
        /// Find the positional argument at the specific position from the parsed argument list.
        /// </summary>
        /// <param name="parsedArguments"></param>
        /// <param name="position"></param>
        /// <param name="lastPositionalArgument"></param>
        /// <returns>
        /// If the command line after the [tab] will not be truncated, the return value could be non-null: Get-Cmdlet [tab] abc
        /// If the command line after the [tab] is truncated, the return value will always be null
        /// </returns>
        private static AstPair FindTargetPositionalArgument(Collection<AstParameterArgumentPair> parsedArguments, int position, out AstPair lastPositionalArgument)
        {
            int index = 0;
            lastPositionalArgument = null;
            foreach (AstParameterArgumentPair pair in parsedArguments)
            {
                if (!pair.ParameterSpecified && index == position)
                    return (AstPair)pair;
                else if (!pair.ParameterSpecified)
                {
                    index++;
                    lastPositionalArgument = (AstPair)pair;
                }
            }

            // Cannot find an existing positional argument at 'position'
            return null;
        }

        /// <summary>
        /// Find the location where 'tab' is typed based on the line and column.
        /// </summary>
        private static ArgumentLocation FindTargetArgumentLocation(Collection<AstParameterArgumentPair> parsedArguments, Token token)
        {
            int position = 0;
            AstParameterArgumentPair prevArg = null;
            foreach (AstParameterArgumentPair pair in parsedArguments)
            {
                switch (pair.ParameterArgumentType)
                {
                    case AstParameterArgumentType.AstPair:
                        {
                            var arg = (AstPair)pair;
                            if (arg.ParameterSpecified)
                            {
                                // Named argument
                                if (arg.Parameter.Extent.StartOffset > token.Extent.StartOffset)
                                {
                                    // case: Get-Cmdlet <tab> -Param abc
                                    return GenerateArgumentLocation(prevArg, position);
                                }

                                if (!arg.ParameterContainsArgument && arg.Argument.Extent.StartOffset > token.Extent.StartOffset)
                                {
                                    // case: Get-Cmdlet -Param <tab> abc
                                    return new ArgumentLocation() { Argument = arg, IsPositional = false, Position = -1 };
                                }
                            }
                            else
                            {
                                // Positional argument
                                if (arg.Argument.Extent.StartOffset > token.Extent.StartOffset)
                                {
                                    // case: Get-Cmdlet <tab> abc
                                    return GenerateArgumentLocation(prevArg, position);
                                }

                                position++;
                            }

                            prevArg = arg;
                        }

                        break;
                    case AstParameterArgumentType.Fake:
                    case AstParameterArgumentType.Switch:
                        {
                            if (pair.Parameter.Extent.StartOffset > token.Extent.StartOffset)
                            {
                                return GenerateArgumentLocation(prevArg, position);
                            }

                            prevArg = pair;
                        }

                        break;
                    case AstParameterArgumentType.AstArray:
                    case AstParameterArgumentType.PipeObject:
                        Diagnostics.Assert(false, "parsed arguments should not contain AstArray and PipeObject");
                        break;
                }
            }

            // The 'tab' should be typed after the last argument
            return GenerateArgumentLocation(prevArg, position);
        }

        /// <summary>
        /// </summary>
        /// <param name="prev">The argument that is right before the 'tab' location.</param>
        /// <param name="position">The number of positional arguments before the 'tab' location.</param>
        /// <returns></returns>
        private static ArgumentLocation GenerateArgumentLocation(AstParameterArgumentPair prev, int position)
        {
            // Tab is typed before the first argument
            if (prev == null)
            {
                return new ArgumentLocation() { Argument = null, IsPositional = true, Position = 0 };
            }

            switch (prev.ParameterArgumentType)
            {
                case AstParameterArgumentType.AstPair:
                case AstParameterArgumentType.Switch:
                    if (!prev.ParameterSpecified)
                        return new ArgumentLocation() { Argument = null, IsPositional = true, Position = position };

                    return prev.Parameter.Extent.Text.EndsWith(':')
                        ? new ArgumentLocation() { Argument = prev, IsPositional = false, Position = -1 }

                        : new ArgumentLocation() { Argument = null, IsPositional = true, Position = position };
                case AstParameterArgumentType.Fake:
                    return new ArgumentLocation() { Argument = prev, IsPositional = false, Position = -1 };
                default:
                    Diagnostics.Assert(false, "parsed arguments should not contain AstArray and PipeObject");
                    return null;
            }
        }

        /// <summary>
        /// Find the location where 'tab' is typed based on the expressionAst.
        /// </summary>
        /// <param name="parsedArguments"></param>
        /// <param name="expAst"></param>
        /// <returns></returns>
        private static ArgumentLocation FindTargetArgumentLocation(Collection<AstParameterArgumentPair> parsedArguments, ExpressionAst expAst)
        {
            Diagnostics.Assert(expAst != null, "Caller needs to make sure expAst is not null");
            int position = 0;
            foreach (AstParameterArgumentPair pair in parsedArguments)
            {
                switch (pair.ParameterArgumentType)
                {
                    case AstParameterArgumentType.AstPair:
                        {
                            AstPair arg = (AstPair)pair;
                            if (arg.ArgumentIsCommandParameterAst)
                                continue;

                            if (arg.ParameterContainsArgument && arg.Argument == expAst)
                            {
                                return new ArgumentLocation() { IsPositional = false, Position = -1, Argument = arg };
                            }

                            if (arg.Argument.GetHashCode() == expAst.GetHashCode())
                            {
                                return arg.ParameterSpecified ?
                                    new ArgumentLocation() { IsPositional = false, Position = -1, Argument = arg } :
                                    new ArgumentLocation() { IsPositional = true, Position = position, Argument = arg };
                            }

                            if (!arg.ParameterSpecified)
                                position++;
                        }

                        break;
                    case AstParameterArgumentType.Fake:
                    case AstParameterArgumentType.Switch:
                        // FakePair and SwitchPair contains no ExpressionAst
                        break;
                    case AstParameterArgumentType.AstArray:
                    case AstParameterArgumentType.PipeObject:
                        Diagnostics.Assert(false, "parsed arguments should not contain AstArray and PipeObject arguments");
                        break;
                }
            }

            // We should be able to find the ExpAst from the parsed argument list, if all parameters was specified correctly.
            // We may try to complete something incorrect
            // ls -Recurse -QQQ qwe<+tab>
            return null;
        }

        private sealed class ArgumentLocation
        {
            internal bool IsPositional { get; set; }
            internal int Position { get; set; }
            internal AstParameterArgumentPair Argument { get; set; }
        }

        #endregion Command Arguments

        #region Filenames

        /// <summary>
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")]
        public static IEnumerable<CompletionResult> CompleteFilename(string fileName)
        {
            var runspace = Runspace.DefaultRunspace;
            if (runspace == null)
            {
                // No runspace, just return no results.
                return CommandCompletion.EmptyCompletionResult;
            }

            var helper = new PowerShellExecutionHelper(PowerShell.Create(RunspaceMode.CurrentRunspace));
            var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext;
            return CompleteFilename(new CompletionContext { WordToComplete = fileName, Helper = helper, ExecutionContext = executionContext });
        }

        internal static IEnumerable<CompletionResult> CompleteFilename(CompletionContext context)
        {
            return CompleteFilename(context, containerOnly: false, extension: null);
        }

        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")]
        internal static IEnumerable<CompletionResult> CompleteFilename(CompletionContext context, bool containerOnly, HashSet<string> extension)
        {
            var wordToComplete = context.WordToComplete;
            var quote = HandleDoubleAndSingleQuote(ref wordToComplete);
            var results = new List<CompletionResult>();

            // First, try to match \\server\share
            var shareMatch = Regex.Match(wordToComplete, "^\\\\\\\\([^\\\\]+)\\\\([^\\\\]*)$");
            if (shareMatch.Success)
            {
                // Only match share names, no filenames.
                var server = shareMatch.Groups[1].Value;
                var sharePattern = WildcardPattern.Get(shareMatch.Groups[2].Value + "*", WildcardOptions.IgnoreCase);
                var ignoreHidden = context.GetOption("IgnoreHiddenShares", @default: false);
                var shares = GetFileShares(server, ignoreHidden);
                foreach (var share in shares)
                {
                    if (sharePattern.IsMatch(share))
                    {
                        string shareFullPath = "\\\\" + server + "\\" + share;
                        if (quote != string.Empty)
                        {
                            shareFullPath = quote + shareFullPath + quote;
                        }

                        results.Add(new CompletionResult(shareFullPath, shareFullPath, CompletionResultType.ProviderContainer, shareFullPath));
                    }
                }
            }
            else
            {
                // We want to prefer relative paths in a completion result unless the user has already
                // specified a drive or portion of the path.
                var executionContext = context.ExecutionContext;
                var defaultRelative = string.IsNullOrWhiteSpace(wordToComplete)
                                      || (wordToComplete.IndexOfAny(Utils.Separators.Directory) != 0 &&
                                          !Regex.Match(wordToComplete, @"^~[\\/]+.*").Success &&
                                          !executionContext.LocationGlobber.IsAbsolutePath(wordToComplete, out _));
                var relativePaths = context.GetOption("RelativePaths", @default: defaultRelative);
                var useLiteralPath = context.GetOption("LiteralPaths", @default: false);

                if (useLiteralPath && LocationGlobber.StringContainsGlobCharacters(wordToComplete))
                {
                    wordToComplete = WildcardPattern.Escape(wordToComplete, Utils.Separators.StarOrQuestion);
                }

                if (!defaultRelative && wordToComplete.Length >= 2 && wordToComplete[1] == ':' && char.IsLetter(wordToComplete[0]) && executionContext != null)
                {
                    // We don't actually need the drive, but the drive must be "mounted" in PowerShell before completion
                    // can succeed.  This call will mount the drive if it wasn't already.
                    executionContext.SessionState.Drive.GetAtScope(wordToComplete.Substring(0, 1), "global");
                }

                var powerShellExecutionHelper = context.Helper;
                powerShellExecutionHelper
                    .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Resolve-Path")
                    .AddParameter("Path", wordToComplete + "*");

                Exception exceptionThrown;
                var psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);

                if (psobjs != null)
                {
                    var isFileSystem = false;
                    var wordContainsProviderId = ProviderSpecified(wordToComplete);

                    if (psobjs.Count > 0)
                    {
                        dynamic firstObj = psobjs[0];
                        var provider = firstObj.Provider as ProviderInfo;
                        isFileSystem = provider != null &&
                                       provider.Name.Equals(FileSystemProvider.ProviderName,
                                                            StringComparison.OrdinalIgnoreCase);
                    }
                    else
                    {
                        try
                        {
                            ProviderInfo provider;
                            if (defaultRelative)
                            {
                                provider = executionContext.EngineSessionState.CurrentDrive.Provider;
                            }
                            else
                            {
                                executionContext.LocationGlobber.GetProviderPath(wordToComplete, out provider);
                            }

                            isFileSystem = provider != null &&
                                           provider.Name.Equals(FileSystemProvider.ProviderName,
                                                                StringComparison.OrdinalIgnoreCase);
                        }
                        catch (Exception)
                        {
                        }
                    }

                    if (isFileSystem)
                    {
                        bool hiddenFilesAreHandled = false;

                        if (psobjs.Count > 0 && !LocationGlobber.StringContainsGlobCharacters(wordToComplete))
                        {
                            string leaf = null;
                            string pathWithoutProvider = wordContainsProviderId
                                    ? wordToComplete.Substring(wordToComplete.IndexOf(':') + 2)
                                    : wordToComplete;

                            try
                            {
                                leaf = Path.GetFileName(pathWithoutProvider);
                            }
                            catch (Exception)
                            {
                            }

                            var notHiddenEntries = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                            string providerPath = null;

                            foreach (dynamic entry in psobjs)
                            {
                                providerPath = entry.ProviderPath;
                                if (string.IsNullOrEmpty(providerPath))
                                {
                                    // This is unexpected. ProviderPath should never be null or an empty string
                                    leaf = null;
                                    break;
                                }

                                if (!notHiddenEntries.Contains(providerPath))
                                {
                                    notHiddenEntries.Add(providerPath);
                                }
                            }

                            if (leaf != null)
                            {
                                leaf = leaf + "*";
                                var parentPath = Path.GetDirectoryName(providerPath);

                                // ProviderPath should be absolute path for FileSystem entries
                                if (!string.IsNullOrEmpty(parentPath))
                                {
                                    string[] entries = null;
                                    try
                                    {
                                        entries = Directory.GetFileSystemEntries(parentPath, leaf, _enumerationOptions);
                                    }
                                    catch (Exception)
                                    {
                                    }

                                    if (entries != null)
                                    {
                                        hiddenFilesAreHandled = true;

                                        if (entries.Length > notHiddenEntries.Count)
                                        {
                                            // Do the iteration only if there are hidden files
                                            foreach (var entry in entries)
                                            {
                                                if (notHiddenEntries.Contains(entry))
                                                    continue;

                                                var fileInfo = new FileInfo(entry);
                                                try
                                                {
                                                    if ((fileInfo.Attributes & FileAttributes.Hidden) != 0)
                                                    {
                                                        PSObject wrapper = PSObject.AsPSObject(entry);
                                                        psobjs.Add(wrapper);
                                                    }
                                                }
                                                catch
                                                {
                                                    // do nothing if can't get file attributes
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        if (!hiddenFilesAreHandled)
                        {
                            powerShellExecutionHelper
                                .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-ChildItem")
                                .AddParameter("Path", wordToComplete + "*")
                                .AddParameter("Hidden", true);

                            var hiddenItems = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
                            if (hiddenItems != null && hiddenItems.Count > 0)
                            {
                                foreach (var hiddenItem in hiddenItems)
                                {
                                    psobjs.Add(hiddenItem);
                                }
                            }
                        }
                    }

                    // Sorting the results by the path
                    var sortedPsobjs = psobjs.OrderBy(a => a, new ItemPathComparer());

                    foreach (PSObject psobj in sortedPsobjs)
                    {
                        object baseObj = PSObject.Base(psobj);
                        string path = null, providerPath = null;

                        // Get the path, the PSObject could be:
                        // 1. a PathInfo object -- results of Resolve-Path
                        // 2. a FileSystemInfo Object -- results of Get-ChildItem
                        // 3. a string -- the path results return by the direct .NET API invocation
                        var baseObjAsPathInfo = baseObj as PathInfo;
                        if (baseObjAsPathInfo != null)
                        {
                            path = baseObjAsPathInfo.Path;
                            providerPath = baseObjAsPathInfo.ProviderPath;
                        }
                        else if (baseObj is FileSystemInfo)
                        {
                            // The target provider is the FileSystem
                            dynamic dirResult = psobj;
                            providerPath = dirResult.FullName;
                            path = wordContainsProviderId ? dirResult.PSPath : providerPath;
                        }
                        else
                        {
                            var baseObjAsString = baseObj as string;
                            if (baseObjAsString != null)
                            {
                                // The target provider is the FileSystem
                                providerPath = baseObjAsString;
                                path = wordContainsProviderId
                                    ? FileSystemProvider.ProviderName + "::" + baseObjAsString
                                    : providerPath;
                            }
                        }

                        if (path == null) continue;
                        if (isFileSystem && providerPath == null) continue;

                        string completionText;
                        if (relativePaths)
                        {
                            try
                            {
                                var sessionStateInternal = executionContext.EngineSessionState;
                                completionText = sessionStateInternal.NormalizeRelativePath(path, sessionStateInternal.CurrentLocation.ProviderPath);
                                string parentDirectory = ".." + StringLiterals.DefaultPathSeparator;
                                if (!completionText.StartsWith(parentDirectory, StringComparison.Ordinal))
                                    completionText = Path.Combine(".", completionText);
                            }
                            catch (Exception)
                            {
                                // The object at the specified path is not accessable, such as c:\hiberfil.sys (for hibernation) or c:\pagefile.sys (for paging)
                                // We ignore those files
                                continue;
                            }
                        }
                        else
                        {
                            completionText = path;
                        }

                        if (ProviderSpecified(completionText) && !wordContainsProviderId)
                        {
                            // Remove the provider id from the path: cd \\scratch2\scratch\dongbw
                            var index = completionText.IndexOf(':');
                            completionText = completionText.Substring(index + 2);
                        }

                        if (CompletionRequiresQuotes(completionText, !useLiteralPath))
                        {
                            var quoteInUse = quote == string.Empty ? "'" : quote;
                            if (quoteInUse == "'")
                            {
                                completionText = completionText.Replace("'", "''");
                            }
                            else
                            {
                                // When double quote is in use, we have to escape the backtip and '$' even when using literal path
                                //   Get-Content -LiteralPath ".\a``g.txt"
                                completionText = completionText.Replace("`", "``");
                                completionText = completionText.Replace("$", "`$");
                            }

                            if (!useLiteralPath)
                            {
                                if (quoteInUse == "'")
                                {
                                    completionText = completionText.Replace("[", "`[");
                                    completionText = completionText.Replace("]", "`]");
                                }
                                else
                                {
                                    completionText = completionText.Replace("[", "``[");
                                    completionText = completionText.Replace("]", "``]");
                                }
                            }

                            completionText = quoteInUse + completionText + quoteInUse;
                        }
                        else if (quote != string.Empty)
                        {
                            completionText = quote + completionText + quote;
                        }

                        if (isFileSystem)
                        {
                            // Use .NET APIs directly to reduce the time overhead
                            var isContainer = Directory.Exists(providerPath);
                            if (containerOnly && !isContainer)
                                continue;

                            if (!containerOnly && !isContainer && !CheckFileExtension(providerPath, extension))
                                continue;

                            string tooltip = providerPath, listItemText = Path.GetFileName(providerPath);
                            results.Add(new CompletionResult(completionText, listItemText,
                                                             isContainer ? CompletionResultType.ProviderContainer : CompletionResultType.ProviderItem,
                                                             tooltip));
                        }
                        else
                        {
                            powerShellExecutionHelper
                                .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item")
                                .AddParameter("LiteralPath", path);
                            var items = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
                            if (items != null && items.Count == 1)
                            {
                                dynamic item = items[0];
                                var isContainer = LanguagePrimitives.ConvertTo<bool>(item.PSIsContainer);

                                if (containerOnly && !isContainer)
                                    continue;

                                powerShellExecutionHelper
                                    .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Convert-Path")
                                    .AddParameter("LiteralPath", item.PSPath);
                                var tooltips = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
                                string tooltip = null, listItemText = item.PSChildName;
                                if (tooltips != null && tooltips.Count == 1)
                                {
                                    tooltip = PSObject.Base(tooltips[0]) as string;
                                }

                                if (string.IsNullOrEmpty(listItemText))
                                {
                                    // For provider items that don't have PSChildName values, such as variable::error
                                    listItemText = item.Name;
                                }

                                results.Add(new CompletionResult(completionText, listItemText,
                                                                 isContainer ? CompletionResultType.ProviderContainer : CompletionResultType.ProviderItem,
                                                                 tooltip ?? path));
                            }
                            else
                            {
                                // We can get here when get-item fails, perhaps due an acl or whatever.
                                results.Add(new CompletionResult(completionText));
                            }
                        }
                    }
                }
            }

            return results;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct SHARE_INFO_1
        {
            public string netname;
            public int type;
            public string remark;
        }

        private const int MAX_PREFERRED_LENGTH = -1;
        private const int NERR_Success = 0;
        private const int ERROR_MORE_DATA = 234;
        private const int STYPE_DISKTREE = 0;
        private const int STYPE_MASK = 0x000000FF;

        private static System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions
        {
            MatchCasing = MatchCasing.CaseInsensitive,
            AttributesToSkip = 0 // Default is to skip Hidden and System files, so we clear this to retain existing behavior
        };

        [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]
        private static extern int NetShareEnum(string serverName, int level, out IntPtr bufptr, int prefMaxLen,
                                               out uint entriesRead, out uint totalEntries, ref uint resumeHandle);

        internal static List<string> GetFileShares(string machine, bool ignoreHidden)
        {
#if UNIX
            return new List<string>();
#else
            IntPtr shBuf;
            uint numEntries;
            uint totalEntries;
            uint resumeHandle = 0;
            int result = NetShareEnum(machine, 1, out shBuf,
                                        MAX_PREFERRED_LENGTH, out numEntries, out totalEntries,
                                        ref resumeHandle);

            var shares = new List<string>();
            if (result == NERR_Success || result == ERROR_MORE_DATA)
            {
                for (int i = 0; i < numEntries; ++i)
                {
                    IntPtr curInfoPtr = (IntPtr)((long)shBuf + (Marshal.SizeOf<SHARE_INFO_1>() * i));
                    SHARE_INFO_1 shareInfo = Marshal.PtrToStructure<SHARE_INFO_1>(curInfoPtr);

                    if ((shareInfo.type & STYPE_MASK) != STYPE_DISKTREE)
                        continue;
                    if (ignoreHidden && shareInfo.netname.EndsWith('$'))
                        continue;
                    shares.Add(shareInfo.netname);
                }
            }

            return shares;
#endif
        }

        private static bool CheckFileExtension(string path, HashSet<string> extension)
        {
            if (extension == null || extension.Count == 0)
                return true;

            var ext = System.IO.Path.GetExtension(path);
            return ext == null || extension.Contains(ext);
        }

        #endregion Filenames

        #region Variable

        /// <summary>
        /// </summary>
        /// <param name="variableName"></param>
        /// <returns></returns>
        public static IEnumerable<CompletionResult> CompleteVariable(string variableName)
        {
            var runspace = Runspace.DefaultRunspace;
            if (runspace == null)
            {
                // No runspace, just return no results.
                return CommandCompletion.EmptyCompletionResult;
            }

            var helper = new PowerShellExecutionHelper(PowerShell.Create(RunspaceMode.CurrentRunspace));
            var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext;
            return CompleteVariable(new CompletionContext { WordToComplete = variableName, Helper = helper, ExecutionContext = executionContext });
        }

        private static readonly string[] s_variableScopes = new string[] { "Global:", "Local:", "Script:", "Private:" };

        private static readonly char[] s_charactersRequiringQuotes = new char[] {
            '-', '`', '&', '@', '\'', '"', '#', '{', '}', '(', ')', '$', ',', ';', '|', '<', '>', ' ', '.', '\\', '/', '\t', '^',
        };

        internal static List<CompletionResult> CompleteVariable(CompletionContext context)
        {
            HashSet<string> hashedResults = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            List<CompletionResult> results = new List<CompletionResult>();

            var wordToComplete = context.WordToComplete;
            var colon = wordToComplete.IndexOf(':');

            var lastAst = context.RelatedAsts?.Last();
            var variableAst = lastAst as VariableExpressionAst;
            var prefix = variableAst != null && variableAst.Splatted ? "@" : "$";

            // Look for variables in the input (e.g. parameters, etc.) before checking session state - these
            // variables might not exist in session state yet.
            var wildcardPattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
            if (lastAst != null)
            {
                Ast parent = lastAst.Parent;
                var findVariablesVisitor = new FindVariablesVisitor { CompletionVariableAst = lastAst };
                while (parent != null)
                {
                    if (parent is IParameterMetadataProvider)
                    {
                        findVariablesVisitor.Top = parent;
                        parent.Visit(findVariablesVisitor);
                    }

                    parent = parent.Parent;
                }

                foreach (Tuple<string, Ast> varAst in findVariablesVisitor.VariableSources)
                {
                    Ast astTarget = null;
                    string userPath = null;

                    VariableExpressionAst variableDefinitionAst = varAst.Item2 as VariableExpressionAst;
                    if (variableDefinitionAst != null)
                    {
                        userPath = varAst.Item1;
                        astTarget = varAst.Item2.Parent;
                    }
                    else
                    {
                        CommandAst commandParameterAst = varAst.Item2 as CommandAst;
                        if (commandParameterAst != null)
                        {
                            userPath = varAst.Item1;
                            astTarget = varAst.Item2;
                        }
                    }

                    if (string.IsNullOrEmpty(userPath))
                    {
                        Diagnostics.Assert(false, "Found a variable source but it was an unknown AST type.");
                    }

                    if (wildcardPattern.IsMatch(userPath))
                    {
                        var completedName = (userPath.IndexOfAny(s_charactersRequiringQuotes) == -1)
                                                ? prefix + userPath
                                                : prefix + "{" + userPath + "}";
                        var tooltip = userPath;
                        var ast = astTarget;

                        while (ast != null)
                        {
                            var parameterAst = ast as ParameterAst;
                            if (parameterAst != null)
                            {
                                var typeConstraint = parameterAst.Attributes.OfType<TypeConstraintAst>().FirstOrDefault();
                                if (typeConstraint != null)
                                {
                                    tooltip = StringUtil.Format("{0}${1}", typeConstraint.Extent.Text, userPath);
                                }

                                break;
                            }

                            var assignmentAst = ast.Parent as AssignmentStatementAst;
                            if (assignmentAst != null)
                            {
                                if (assignmentAst.Left == ast)
                                {
                                    tooltip = ast.Extent.Text;
                                }

                                break;
                            }

                            var commandAst = ast as CommandAst;
                            if (commandAst != null)
                            {
                                PSTypeName discoveredType = AstTypeInference.InferTypeOf(ast, context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval).FirstOrDefault<PSTypeName>();
                                if (discoveredType != null)
                                {
                                    tooltip = StringUtil.Format("[{0}]${1}", discoveredType.Name, userPath);
                                }

                                break;
                            }

                            ast = ast.Parent;
                        }

                        AddUniqueVariable(hashedResults, results, completedName, userPath, tooltip);
                    }
                }
            }

            string pattern;
            string provider;
            if (colon == -1)
            {
                pattern = "variable:" + wordToComplete + "*";
                provider = string.Empty;
            }
            else
            {
                provider = wordToComplete.Substring(0, colon + 1);
                if (s_variableScopes.Contains(provider, StringComparer.OrdinalIgnoreCase))
                {
                    pattern = "variable:" + wordToComplete.Substring(colon + 1) + "*";
                }
                else
                {
                    pattern = wordToComplete + "*";
                }
            }

            var powerShellExecutionHelper = context.Helper;
            powerShellExecutionHelper
                .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item").AddParameter("Path", pattern)
                .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Name");

            Exception exceptionThrown;
            var psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
            if (psobjs != null)
            {
                foreach (dynamic psobj in psobjs)
                {
                    var name = psobj.Name as string;
                    if (!string.IsNullOrEmpty(name))
                    {
                        var tooltip = name;
                        var variable = PSObject.Base(psobj) as PSVariable;
                        if (variable != null)
                        {
                            var value = variable.Value;
                            if (value != null)
                            {
                                tooltip = StringUtil.Format("[{0}]${1}",
                                                            ToStringCodeMethods.Type(value.GetType(),
                                                                                     dropNamespaces: true), name);
                            }
                        }

                        var completedName = (name.IndexOfAny(s_charactersRequiringQuotes) == -1)
                                                ? prefix + provider + name
                                                : prefix + "{" + provider + name + "}";
                        AddUniqueVariable(hashedResults, results, completedName, name, tooltip);
                    }
                }
            }

            if (colon == -1 && "env".StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase))
            {
                powerShellExecutionHelper
                    .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item").AddParameter("Path", "env:*")
                    .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Key");

                psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
                if (psobjs != null)
                {
                    foreach (dynamic psobj in psobjs)
                    {
                        var name = psobj.Name as string;
                        if (!string.IsNullOrEmpty(name))
                        {
                            name = "env:" + name;
                            var completedName = (name.IndexOfAny(s_charactersRequiringQuotes) == -1)
                                                    ? prefix + name
                                                    : prefix + "{" + name + "}";
                            AddUniqueVariable(hashedResults, results, completedName, name, "[string]" + name);
                        }
                    }
                }
            }

            // Return variables already in session state first, because we can sometimes give better information,
            // like the variables type.
            foreach (var specialVariable in s_specialVariablesCache.Value)
            {
                if (wildcardPattern.IsMatch(specialVariable))
                {
                    var completedName = (specialVariable.IndexOfAny(s_charactersRequiringQuotes) == -1)
                                            ? prefix + specialVariable
                                            : prefix + "{" + specialVariable + "}";

                    AddUniqueVariable(hashedResults, results, completedName, specialVariable, specialVariable);
                }
            }

            if (colon == -1)
            {
                // If no drive was specified, then look for matching drives/scopes
                pattern = wordToComplete + "*";
                powerShellExecutionHelper
                    .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-PSDrive").AddParameter("Name", pattern)
                    .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Name");
                psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown);
                if (psobjs != null)
                {
                    foreach (var psobj in psobjs)
                    {
                        var driveInfo = PSObject.Base(psobj) as PSDriveInfo;
                        if (driveInfo != null)
                        {
                            var name = driveInfo.Name;
                            if (name != null && !string.IsNullOrWhiteSpace(name) && name.Length > 1)
                            {
                                var completedName = (name.IndexOfAny(s_charactersRequiringQuotes) == -1)
                                                        ? prefix + name + ":"
                                                        : prefix + "{" + name + ":}";

                                var tooltip = string.IsNullOrEmpty(driveInfo.Description) ? name : driveInfo.Description;
                                AddUniqueVariable(hashedResults, results, completedName, name, tooltip);
                            }
                        }
                    }
                }

                var scopePattern = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase);
                foreach (var scope in s_variableScopes)
                {
                    if (scopePattern.IsMatch(scope))
                    {
                        var completedName = (scope.IndexOfAny(s_charactersRequiringQuotes) == -1)
                                                ? prefix + scope
                                                : prefix + "{" + scope + "}";
                        AddUniqueVariable(hashedResults, results, completedName, scope, scope);
                    }
                }
            }

            return results;
        }

        private static void AddUniqueVariable(HashSet<string> hashedResults, List<CompletionResult> results, string completionText, string listItemText, string tooltip)
        {
            if (!hashedResults.Contains(completionText))
            {
                hashedResults.Add(completionText);
                results.Add(new CompletionResult(completionText, listItemText, CompletionResultType.Variable, tooltip));
            }
        }

        private class FindVariablesVisitor : AstVisitor
        {
            internal Ast Top;
            internal Ast CompletionVariableAst;
            internal readonly List<Tuple<string, Ast>> VariableSources = new List<Tuple<string, Ast>>();

            public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
            {
                if (variableExpressionAst != CompletionVariableAst)
                {
                    VariableSources.Add(new Tuple<string, Ast>(variableExpressionAst.VariablePath.UserPath, variableExpressionAst));
                }

                return AstVisitAction.Continue;
            }

            public override AstVisitAction VisitCommand(CommandAst commandAst)
            {
                // MSFT: 784739 Stack overflow during tab completion of pipeline variable
                // $null | % -pv p { $p<TAB> -> In this case $p is pipelinevariable
                // and is used in the same command. PipelineVariables are not available
                // in the command they are assigned in. Hence the following code ignores
                // if the variable being completed is in the command extent.
                if ((commandAst != CompletionVariableAst) && (!CompletionVariableAst.Extent.IsWithin(commandAst.Extent)))
                {
                    string[] desiredParameters = new string[] { "PV", "PipelineVariable", "OV", "OutVariable" };

                    StaticBindingResult bindingResult = StaticParameterBinder.BindCommand(commandAst, false, desiredParameters);
                    if (bindingResult != null)
                    {
                        ParameterBindingResult parameterBindingResult;

                        foreach (string commandVariableParameter in desiredParameters)
                        {
                            if (bindingResult.BoundParameters.TryGetValue(commandVariableParameter, out parameterBindingResult))
                            {
                                VariableSources.Add(new Tuple<string, Ast>((string)parameterBindingResult.ConstantValue, commandAst));
                            }
                        }
                    }
                }

                return AstVisitAction.Continue;
            }

            public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
            {
                return functionDefinitionAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue;
            }

            public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)
            {
                return scriptBlockExpressionAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue;
            }

            public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst)
            {
                return scriptBlockAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue;
            }
        }

        private static readonly Lazy<SortedSet<string>> s_specialVariablesCache = new Lazy<SortedSet<string>>(BuildSpecialVariablesCache);

        private static SortedSet<string> BuildSpecialVariablesCache()
        {
            var result = new SortedSet<string>();
            foreach (var member in typeof(SpecialVariables).GetFields(BindingFlags.NonPublic | BindingFlags.Static))
            {
                if (member.FieldType.Equals(typeof(string)))
                {
                    result.Add((string)member.GetValue(null));
                }
            }

            return result;
        }

        #endregion Variables

        #region Comments

        // Complete the history entries
        internal static List<CompletionResult> CompleteComment(CompletionContext context)
        {
            List<CompletionResult> results = new List<CompletionResult>();

            Match matchResult = Regex.Match(context.WordToComplete, @"^#([\w\-]*)$");
            if (!matchResult.Success) { return results; }

            string wordToComplete = matchResult.Groups[1].Value;
            Collection<PSObject> psobjs;

            int entryId;
            if (Regex.IsMatch(wordToComplete, @"^[0-9]+$") && LanguagePrimitives.TryConvertTo(wordToComplete, out entryId))
            {
                context.Helper.AddCommandWithPreferenceSetting("Get-History", typeof(GetHistoryCommand)).AddParameter("Id", entryId);
                psobjs = context.Helper.ExecuteCurrentPowerShell(out _);

                if (psobjs != null && psobjs.Count == 1)
                {
                    var historyInfo = PSObject.Base(psobjs[0]) as HistoryInfo;
                    if (historyInfo != null)
                    {
                        var commandLine = historyInfo.CommandLine;
                        if (!string.IsNullOrEmpty(commandLine))
                        {
                            // var tooltip = "Id: " + historyInfo.Id + "\n" +
                            //               "ExecutionStatus: " + historyInfo.ExecutionStatus + "\n" +
                            //               "StartExecutionTime: " + historyInfo.StartExecutionTime + "\n" +
                            //               "EndExecutionTime: " + historyInfo.EndExecutionTime + "\n";
                            // Use the commandLine as the Tooltip in case the commandLine is multiple lines of scripts
                            results.Add(new CompletionResult(commandLine, commandLine, CompletionResultType.History, commandLine));
                        }
                    }
                }

                return results;
            }

            wordToComplete = "*" + wordToComplete + "*";
            context.Helper.AddCommandWithPreferenceSetting("Get-History", typeof(GetHistoryCommand));

            psobjs = context.Helper.ExecuteCurrentPowerShell(out _);
            var pattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase);

            if (psobjs != null)
            {
                for (int index = psobjs.Count - 1; index >= 0; index--)
                {
                    var psobj = psobjs[index];
                    var historyInfo = PSObject.Base(psobj) as HistoryInfo;
                    if (historyInfo == null) continue;

                    var commandLine = historyInfo.CommandLine;
                    if (!string.IsNullOrEmpty(commandLine) && pattern.IsMatch(commandLine))
                    {
                        // var tooltip = "Id: " + historyInfo.Id + "\n" +
                        //               "ExecutionStatus: " + historyInfo.ExecutionStatus + "\n" +
                        //               "StartExecutionTime: " + historyInfo.StartExecutionTime + "\n" +
                        //               "EndExecutionTime: " + historyInfo.EndExecutionTime + "\n";
                        // Use the commandLine as the Tooltip in case the commandLine is multiple lines of scripts
                        results.Add(new CompletionResult(commandLine, commandLine, CompletionResultType.History, commandLine));
                    }
                }
            }

            return results;
        }

        #endregion Comments

        #region Members

        // List of extension methods <MethodName, Signature>
        private static readonly List<Tuple<string, string>> s_extensionMethods =
            new List<Tuple<string, string>>
                {
                    new Tuple<string, string>("Where", "Where({ expression } [, mode [, numberToReturn]])"),
                    new Tuple<string, string>("ForEach", "ForEach(expression [, arguments...])")
                };
        // List of DSC collection-value variables
        private static readonly HashSet<string> s_dscCollectionVariables =
            new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "SelectedNodes", "AllNodes" };

        internal static List<CompletionResult> CompleteMember(CompletionContext context, bool @static)
        {
            // If we get here, we know that either:
            //   * the cursor appeared immediately after a member access token ('.' or '::').
            //   * the parent of the ast on the cursor was a member expression.
            //
            // In the first case, we have 2 possibilities:
            //   * the last ast is an error ast because no member name was entered and we were in expression context
            //   * the last ast is a string constant, with something like:   echo $foo.

            var results = new List<CompletionResult>();

            var lastAst = context.RelatedAsts.Last();
            var lastAstAsMemberExpr = lastAst as MemberExpressionAst;
            Ast memberNameCandidateAst = null;
            ExpressionAst targetExpr = null;
            if (lastAstAsMemberExpr != null)
            {
                // If the cursor is not inside the member name in the member expression, assume
                // that the user had incomplete input, but the parser got lucky and succeeded parsing anyway.
                if (context.TokenAtCursor.Extent.StartOffset >= lastAstAsMemberExpr.Member.Extent.StartOffset)
                {
                    memberNameCandidateAst = lastAstAsMemberExpr.Member;
                }

                targetExpr = lastAstAsMemberExpr.Expression;
            }
            else
            {
                memberNameCandidateAst = lastAst;
            }

            var memberNameAst = memberNameCandidateAst as StringConstantExpressionAst;

            var memberName = "*";
            if (memberNameAst != null)
            {
                // Make sure to correctly handle: echo $foo.
                if (!memberNameAst.Value.Equals(".", StringComparison.OrdinalIgnoreCase) && !memberNameAst.Value.Equals("::", StringComparison.OrdinalIgnoreCase))
                {
                    memberName = memberNameAst.Value + "*";
                }
            }
            else if (!(lastAst is ErrorExpressionAst) && targetExpr == null)
            {
                // I don't think we can complete anything interesting
                return results;
            }

            var commandAst = lastAst.Parent as CommandAst;
            if (commandAst != null)
            {
                int i;
                for (i = commandAst.CommandElements.Count - 1; i >= 0; --i)
                {
                    if (commandAst.CommandElements[i] == lastAst)
                    {
                        break;
                    }
                }

                var nextToLastAst = commandAst.CommandElements[i - 1];
                var nextToLastExtent = nextToLastAst.Extent;
                var lastExtent = lastAst.Extent;
                if (nextToLastExtent.EndLineNumber == lastExtent.StartLineNumber &&
                    nextToLastExtent.EndColumnNumber == lastExtent.StartColumnNumber)
                {
                    targetExpr = nextToLastAst as ExpressionAst;
                }
            }
            else if (lastAst.Parent is MemberExpressionAst)
            {
                // If 'targetExpr' has already been set, we should skip this step. This is for some member completion
                // cases in ISE. In ISE, we may add a new statement in the middle of existing statements as follows:
                //     $xml = New-Object Xml
                //     $xml.
                //     $xml.Save("C:\data.xml")
                // In this example, we add $xml. between two existing statements, and the 'lastAst' in this case is
                // a MemberExpressionAst '$xml.$xml', whose parent is still a MemberExpressionAst '$xml.$xml.Save'.
                // But here we DO NOT want to re-assign 'targetExpr' to be '$xml.$xml'. 'targetExpr' in this case
                // should be '$xml'.
                if (targetExpr == null)
                {
                    var memberExprAst = (MemberExpressionAst)lastAst.Parent;
                    targetExpr = memberExprAst.Expression;
                }
            }
            else if (lastAst.Parent is BinaryExpressionAst && context.TokenAtCursor.Kind.Equals(TokenKind.Multiply))
            {
                var memberExprAst = ((BinaryExpressionAst)lastAst.Parent).Left as MemberExpressionAst;
                if (memberExprAst != null)
                {
                    targetExpr = memberExprAst.Expression;
                    if (memberExprAst.Member is StringConstantExpressionAst)
                    {
                        memberName = ((StringConstantExpressionAst)memberExprAst.Member).Value + "*";
                    }
                }
            }

            if (targetExpr == null)
            {
                // Not sure what we have, but we're not looking for members.
                return results;
            }

            if (IsSplattedVariable(targetExpr))
            {
                // It's splatted variable, member expansion is not useful
                return results;
            }

            CompleteMemberHelper(@static, memberName, targetExpr, context, results);

            if (results.Count == 0)
            {
                PSTypeName[] inferredTypes = null;

                if (@static)
                {
                    var typeExpr = targetExpr as TypeExpressionAst;
                    if (typeExpr != null)
                    {
                        inferredTypes = new[] { new PSTypeName(typeExpr.TypeName) };
                    }
                }
                else
                {
                    inferredTypes = AstTypeInference.InferTypeOf(targetExpr, context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval).ToArray();
                }

                if (inferredTypes != null && inferredTypes.Length > 0)
                {
                    // Use inferred types if we have any
                    CompleteMemberByInferredType(context.TypeInferenceContext, inferredTypes, results, memberName, filter: null, isStatic: @static);
                }
                else
                {
                    // Handle special DSC collection variables to complete the extension methods 'Where' and 'ForEach'
                    // e.g. Configuration foo { node $AllNodes.<tab> --> $AllNodes.Where(
                    var variableAst = targetExpr as VariableExpressionAst;
                    var memberExprAst = targetExpr as MemberExpressionAst;
                    bool shouldAddExtensionMethods = false;

                    // We complete against extension methods 'Where' and 'ForEach' for the following DSC variables
                    // $SelectedNodes, $AllNodes, $ConfigurationData.AllNodes
                    if (variableAst != null)
                    {
                        // Handle $SelectedNodes and $AllNodes
                        var variablePath = variableAst.VariablePath;
                        if (variablePath.IsVariable && s_dscCollectionVariables.Contains(variablePath.UserPath) && IsInDscContext(variableAst))
                        {
                            shouldAddExtensionMethods = true;
                        }
                    }
                    else if (memberExprAst != null)
                    {
                        // Handle $ConfigurationData.AllNodes
                        var member = memberExprAst.Member as StringConstantExpressionAst;
                        if (IsConfigurationDataVariable(memberExprAst.Expression) && member != null &&
                            string.Equals("AllNodes", member.Value, StringComparison.OrdinalIgnoreCase) &&
                            IsInDscContext(memberExprAst))
                        {
                            shouldAddExtensionMethods = true;
                        }
                    }

                    if (shouldAddExtensionMethods)
                    {
                        CompleteExtensionMethods(memberName, results);
                    }
                }

                if (results.Count == 0)
                {
                    // Handle '$ConfigurationData' specially to complete 'AllNodes' for it
                    if (IsConfigurationDataVariable(targetExpr) && IsInDscContext(targetExpr))
                    {
                        var pattern = WildcardPattern.Get(memberName, WildcardOptions.IgnoreCase);
                        if (pattern.IsMatch("AllNodes"))
                        {
                            results.Add(new CompletionResult("AllNodes", "AllNodes", CompletionResultType.Property, "AllNodes"));
                        }
                    }
                }
            }

            return results;
        }

        /// <summary>
        /// Complete members against extension methods 'Where' and 'ForEach'
        /// </summary>
        private static void CompleteExtensionMethods(string memberName, List<CompletionResult> results)
        {
            var pattern = WildcardPattern.Get(memberName, WildcardOptions.IgnoreCase);
            CompleteExtensionMethods(pattern, results);
        }

        /// <summary>
        /// Complete members against extension methods 'Where' and 'ForEach' based on the given pattern.
        /// </summary>
        private static void CompleteExtensionMethods(WildcardPattern pattern, List<CompletionResult> results)
        {
            results.AddRange(from member in s_extensionMethods
                             where pattern.IsMatch(member.Item1)
                             select
                                 new CompletionResult(member.Item1 + "(", member.Item1,
                                                      CompletionResultType.Method, member.Item2));
        }

        /// <summary>
        /// Verify if an expression Ast is representing the $ConfigurationData variable.
        /// </summary>
        private static bool IsConfigurationDataVariable(ExpressionAst targetExpr)
        {
            var variableExpr = targetExpr as VariableExpressionAst;
            if (variableExpr != null)
            {
                var varPath = variableExpr.VariablePath;
                if (varPath.IsVariable &&
                    varPath.UserPath.Equals("ConfigurationData", StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Verify if an expression Ast is within a configuration definition.
        /// </summary>
        private static bool IsInDscContext(ExpressionAst expression)
        {
            return Ast.GetAncestorAst<ConfigurationDefinitionAst>(expression) != null;
        }

        internal static void CompleteMemberByInferredType(TypeInferenceContext context, IEnumerable<PSTypeName> inferredTypes, List<CompletionResult> results, string memberName, Func<object, bool> filter, bool isStatic)
        {
            bool extensionMethodsAdded = false;
            HashSet<string> typeNameUsed = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            WildcardPattern memberNamePattern = WildcardPattern.Get(memberName, WildcardOptions.IgnoreCase);
            foreach (var psTypeName in inferredTypes)
            {
                if (typeNameUsed.Contains(psTypeName.Name))
                {
                    continue;
                }

                typeNameUsed.Add(psTypeName.Name);
                var members = context.GetMembersByInferredType(psTypeName, isStatic, filter);
                foreach (var member in members)
                {
                    AddInferredMember(member, memberNamePattern, results);
                }

                // Check if we need to complete against the extension methods 'Where' and 'ForEach'
                if (!extensionMethodsAdded && psTypeName.Type != null && IsStaticTypeEnumerable(psTypeName.Type))
                {
                    // Complete extension methods 'Where' and 'ForEach' for Enumerable types
                    extensionMethodsAdded = true;
                    CompleteExtensionMethods(memberNamePattern, results);
                }
            }

            if (results.Count > 0)
            {
                // Sort the results
                var powerShellExecutionHelper = context.Helper;
                powerShellExecutionHelper
                    .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object")
                    .AddParameter("Property", new[] { "ResultType", "ListItemText" })
                    .AddParameter("Unique");
                Exception unused;
                var sortedResults = powerShellExecutionHelper.ExecuteCurrentPowerShell(out unused, results);
                results.Clear();
                results.AddRange(sortedResults.Select(psobj => PSObject.Base(psobj) as CompletionResult));
            }
        }

        private static void AddInferredMember(object member, WildcardPattern memberNamePattern, List<CompletionResult> results)
        {
            string memberName = null;
            bool isMethod = false;
            Func<string> getToolTip = null;
            var propertyInfo = member as PropertyInfo;
            if (propertyInfo != null)
            {
                memberName = propertyInfo.Name;
                getToolTip = () => ToStringCodeMethods.Type(propertyInfo.PropertyType) + " " + memberName
                    + " { " + (propertyInfo.GetGetMethod() != null ? "get; " : string.Empty)
                    + (propertyInfo.GetSetMethod() != null ? "set; " : string.Empty) + "}";
            }

            var fieldInfo = member as FieldInfo;
            if (fieldInfo != null)
            {
                memberName = fieldInfo.Name;
                getToolTip = () => ToStringCodeMethods.Type(fieldInfo.FieldType) + " " + memberName;
            }

            var methodCacheEntry = member as DotNetAdapter.MethodCacheEntry;
            if (methodCacheEntry != null)
            {
                memberName = methodCacheEntry[0].method.Name;
                isMethod = true;
                getToolTip = () => string.Join("\n", methodCacheEntry.methodInformationStructures.Select(m => m.methodDefinition));
            }

            var psMemberInfo = member as PSMemberInfo;
            if (psMemberInfo != null)
            {
                memberName = psMemberInfo.Name;
                isMethod = member is PSMethodInfo;
                getToolTip = psMemberInfo.ToString;
            }

            var cimProperty = member as CimPropertyDeclaration;
            if (cimProperty != null)
            {
                memberName = cimProperty.Name;
                isMethod = false;
                getToolTip = () => GetCimPropertyToString(cimProperty);
            }

            var memberAst = member as MemberAst;
            if (memberAst != null)
            {
                memberName = memberAst is CompilerGeneratedMemberFunctionAst ? "new" : memberAst.Name;
                isMethod = memberAst is FunctionMemberAst || memberAst is CompilerGeneratedMemberFunctionAst;
                getToolTip = memberAst.GetTooltip;
            }

            if (memberName == null || !memberNamePattern.IsMatch(memberName))
            {
                return;
            }

            var completionResultType = isMethod ? CompletionResultType.Method : CompletionResultType.Property;
            var completionText = isMethod ? memberName + "(" : memberName;

            results.Add(new CompletionResult(completionText, memberName, completionResultType, getToolTip()));
        }

        private static string GetCimPropertyToString(CimPropertyDeclaration cimProperty)
        {
            string type;
            switch (cimProperty.CimType)
            {
                case Microsoft.Management.Infrastructure.CimType.DateTime:
                case Microsoft.Management.Infrastructure.CimType.Instance:
                case Microsoft.Management.Infrastructure.CimType.Reference:
                case Microsoft.Management.Infrastructure.CimType.DateTimeArray:
                case Microsoft.Management.Infrastructure.CimType.InstanceArray:
                case Microsoft.Management.Infrastructure.CimType.ReferenceArray:
                    type = "CimInstance#" + cimProperty.CimType.ToString();
                    break;

                default:
                    type = ToStringCodeMethods.Type(CimConverter.GetDotNetType(cimProperty.CimType));
                    break;
            }

            bool isReadOnly = (CimFlags.ReadOnly == (cimProperty.Flags & CimFlags.ReadOnly));
            return type + " " + cimProperty.Name + " { get; " + (isReadOnly ? "}" : "set; }");
        }

        private static bool IsWriteablePropertyMember(object member)
        {
            var propertyInfo = member as PropertyInfo;
            if (propertyInfo != null)
            {
                return propertyInfo.CanWrite;
            }

            var psPropertyInfo = member as PSPropertyInfo;
            if (psPropertyInfo != null)
            {
                return psPropertyInfo.IsSettable;
            }

            return false;
        }

        internal static bool IsPropertyMember(object member)
        {
            return member is PropertyInfo
                   || member is FieldInfo
                   || member is PSPropertyInfo
                   || member is CimPropertyDeclaration
                   || member is PropertyMemberAst;
        }

        private static bool IsMemberHidden(object member)
        {
            var psMemberInfo = member as PSMemberInfo;
            if (psMemberInfo != null)
                return psMemberInfo.IsHidden;

            var memberInfo = member as MemberInfo;
            if (memberInfo != null)
                return memberInfo.GetCustomAttributes(typeof(HiddenAttribute), false).Length > 0;

            var propertyMemberAst = member as PropertyMemberAst;
            if (propertyMemberAst != null)
                return propertyMemberAst.IsHidden;

            var functionMemberAst = member as FunctionMemberAst;
            if (functionMemberAst != null)
                return functionMemberAst.IsHidden;

            return false;
        }

        private static bool IsConstructor(object member)
        {
            var psMethod = member as PSMethod;
            if (psMethod != null)
            {
                var methodCacheEntry = psMethod.adapterData as DotNetAdapter.MethodCacheEntry;
                if (methodCacheEntry != null)
                {
                    return methodCacheEntry.methodInformationStructures[0].method.IsConstructor;
                }
            }

            return false;
        }

        #endregion Members

        #region Types

        private abstract class TypeCompletionBase
        {
            internal abstract CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix);
            internal abstract CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix, string namespaceToRemove);

            internal static string RemoveBackTick(string typeName)
            {
                var backtick = typeName.LastIndexOf('`');
                return backtick == -1 ? typeName : typeName.Substring(0, backtick);
            }
        }

        /// <summary>
        /// In OneCore PS, there is no way to retrieve all loaded assemblies. But we have the type catalog dictionary
        /// which contains the full type names of all available CoreCLR .NET types. We can extract the necessary info
        /// from the full type names to make type name auto-completion work.
        /// This type represents a non-generic type for type name completion. It only contains information that can be
        /// inferred from the full type name.
        /// </summary>
        private class TypeCompletionInStringFormat : TypeCompletionBase
        {
            /// <summary>
            /// Get the full type name of the type represented by this instance.
            /// </summary>
            internal string FullTypeName;

            /// <summary>
            /// Get the short type name of the type represented by this instance.
            /// </summary>
            internal string ShortTypeName
            {
                get
                {
                    if (_shortTypeName == null)
                    {
                        int lastDotIndex = FullTypeName.LastIndexOf('.');
                        int lastPlusIndex = FullTypeName.LastIndexOf('+');
                        _shortTypeName = lastPlusIndex != -1
                                           ? FullTypeName.Substring(lastPlusIndex + 1)
                                           : FullTypeName.Substring(lastDotIndex + 1);
                    }

                    return _shortTypeName;
                }
            }

            private string _shortTypeName;

            /// <summary>
            /// Get the namespace of the type represented by this instance.
            /// </summary>
            internal string Namespace
            {
                get
                {
                    if (_namespace == null)
                    {
                        int lastDotIndex = FullTypeName.LastIndexOf('.');
                        _namespace = FullTypeName.Substring(0, lastDotIndex);
                    }

                    return _namespace;
                }
            }

            private string _namespace;

            /// <summary>
            /// Construct the CompletionResult based on the information of this instance.
            /// </summary>
            internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix)
            {
                return GetCompletionResult(keyMatched, prefix, suffix, null);
            }

            /// <summary>
            /// Construct the CompletionResult based on the information of this instance.
            /// </summary>
            internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix, string namespaceToRemove)
            {
                string completion = string.IsNullOrEmpty(namespaceToRemove)
                                        ? FullTypeName
                                        : FullTypeName.Substring(namespaceToRemove.Length + 1);

                string listItem = ShortTypeName;
                string tooltip = FullTypeName;

                return new CompletionResult(prefix + completion + suffix, listItem, CompletionResultType.Type, tooltip);
            }
        }

        /// <summary>
        /// In OneCore PS, there is no way to retrieve all loaded assemblies. But we have the type catalog dictionary
        /// which contains the full type names of all available CoreCLR .NET types. We can extract the necessary info
        /// from the full type names to make type name auto-completion work.
        /// This type represents a generic type for type name completion. It only contains information that can be
        /// inferred from the full type name.
        /// </summary>
        private class GenericTypeCompletionInStringFormat : TypeCompletionInStringFormat
        {
            /// <summary>
            /// Get the number of generic type arguments required by the type represented by this instance.
            /// </summary>
            private int GenericArgumentCount
            {
                get
                {
                    if (_genericArgumentCount == 0)
                    {
                        var backtick = FullTypeName.LastIndexOf('`');
                        var argCount = FullTypeName.Substring(backtick + 1);
                        _genericArgumentCount = LanguagePrimitives.ConvertTo<int>(argCount);
                    }

                    return _genericArgumentCount;
                }
            }

            private int _genericArgumentCount = 0;

            /// <summary>
            /// Construct the CompletionResult based on the information of this instance.
            /// </summary>
            internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix)
            {
                return GetCompletionResult(keyMatched, prefix, suffix, null);
            }

            /// <summary>
            /// Construct the CompletionResult based on the information of this instance.
            /// </summary>
            internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix, string namespaceToRemove)
            {
                string fullNameWithoutBacktip = RemoveBackTick(FullTypeName);
                string completion = string.IsNullOrEmpty(namespaceToRemove)
                                        ? fullNameWithoutBacktip
                                        : fullNameWithoutBacktip.Substring(namespaceToRemove.Length + 1);

                string typeName = RemoveBackTick(ShortTypeName);
                var listItem = typeName + "<>";

                var tooltip = new StringBuilder();
                tooltip.Append(fullNameWithoutBacktip);
                tooltip.Append('[');

                for (int i = 0; i < GenericArgumentCount; i++)
                {
                    if (i != 0) tooltip.Append(", ");
                    tooltip.Append(GenericArgumentCount == 1
                                       ? "T"
                                       : string.Format(CultureInfo.InvariantCulture, "T{0}", i + 1));
                }

                tooltip.Append(']');

                return new CompletionResult(prefix + completion + suffix, listItem, CompletionResultType.Type, tooltip.ToString());
            }
        }

        /// <summary>
        /// This type represents a non-generic type for type name completion. It contains the actual type instance.
        /// </summary>
        private class TypeCompletion : TypeCompletionBase
        {
            internal Type Type;

            protected string GetTooltipPrefix()
            {
                if (typeof(Delegate).IsAssignableFrom(Type))
                    return "Delegate ";
                if (Type.IsInterface)
                    return "Interface ";
                if (Type.IsClass)
                    return "Class ";
                if (Type.IsEnum)
                    return "Enum ";
                if (typeof(ValueType).IsAssignableFrom(Type))
                    return "Struct ";

                return string.Empty; // what other interesting types are there?
            }

            internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix)
            {
                return GetCompletionResult(keyMatched, prefix, suffix, null);
            }

            internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix, string namespaceToRemove)
            {
                string completion = ToStringCodeMethods.Type(Type, false, keyMatched);

                // If the completion included a namespace and ToStringCodeMethods.Type found
                // an accelerator, then just use the type's FullName instead because the user
                // probably didn't want the accelerator.
                if (keyMatched.Contains('.') && !completion.Contains('.'))
                {
                    completion = Type.FullName;
                }

                if (!string.IsNullOrEmpty(namespaceToRemove) && completion.Equals(Type.FullName, StringComparison.OrdinalIgnoreCase))
                {
                    // Remove the namespace only if the completion text contains namespace
                    completion = completion.Substring(namespaceToRemove.Length + 1);
                }

                string listItem = Type.Name;
                string tooltip = GetTooltipPrefix() + Type.FullName;

                return new CompletionResult(prefix + completion + suffix, listItem, CompletionResultType.Type, tooltip);
            }
        }

        /// <summary>
        /// This type represents a generic type for type name completion. It contains the actual type instance.
        /// </summary>
        private class GenericTypeCompletion : TypeCompletion
        {
            internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix)
            {
                return GetCompletionResult(keyMatched, prefix, suffix, null);
            }

            internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix, string namespaceToRemove)
            {
                string fullNameWithoutBacktip = RemoveBackTick(Type.FullName);
                string completion = string.IsNullOrEmpty(namespaceToRemove)
                                        ? fullNameWithoutBacktip
                                        : fullNameWithoutBacktip.Substring(namespaceToRemove.Length + 1);

                string typeName = RemoveBackTick(Type.Name);
                var listItem = typeName + "<>";

                var tooltip = new StringBuilder();
                tooltip.Append(GetTooltipPrefix());
                tooltip.Append(fullNameWithoutBacktip);
                tooltip.Append('[');
                var genericParameters = Type.GetGenericArguments();
                for (int i = 0; i < genericParameters.Length; i++)
                {
                    if (i != 0) tooltip.Append(", ");
                    tooltip.Append(genericParameters[i].Name);
                }

                tooltip.Append(']');

                return new CompletionResult(prefix + completion + suffix, listItem, CompletionResultType.Type, tooltip.ToString());
            }
        }

        /// <summary>
        /// This type represents a namespace for namespace completion.
        /// </summary>
        private class NamespaceCompletion : TypeCompletionBase
        {
            internal string Namespace;

            internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix)
            {
                var listItemText = Namespace;
                var dotIndex = listItemText.LastIndexOf('.');
                if (dotIndex != -1)
                {
                    listItemText = listItemText.Substring(dotIndex + 1);
                }

                return new CompletionResult(prefix + Namespace + suffix, listItemText, CompletionResultType.Namespace, "Namespace " + Namespace);
            }

            internal override CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix, string namespaceToRemove)
            {
                return GetCompletionResult(keyMatched, prefix, suffix);
            }
        }

        private class TypeCompletionMapping
        {
            // The Key is the string we'll be searching on.  It could complete to various things.
            internal string Key;
            internal List<TypeCompletionBase> Completions = new List<TypeCompletionBase>();
        }

        private static TypeCompletionMapping[][] s_typeCache;

        private static TypeCompletionMapping[][] InitializeTypeCache()
        {
            #region Process_TypeAccelerators

            var entries = new Dictionary<string, TypeCompletionMapping>(StringComparer.OrdinalIgnoreCase);
            foreach (var type in TypeAccelerators.Get)
            {
                TypeCompletionMapping entry;
                var typeCompletionInstance = new TypeCompletion { Type = type.Value };

                if (entries.TryGetValue(type.Key, out entry))
                {
                    // Check if this accelerator type is already included in the mapping entry referenced by the same key.
                    Type acceleratorType = type.Value;
                    bool typeAlreadyIncluded = entry.Completions.Any(
                        item =>
                            {
                                var typeCompletion = item as TypeCompletion;
                                return typeCompletion != null && typeCompletion.Type == acceleratorType;
                            });

                    // If it's already included, skip it.
                    // This may happen when an accelerator name is the same as the short name of the type it represents,
                    // and aslo that type has more than one accelerator names. For example:
                    //    "float"  -> System.Single
                    //    "single" -> System.Single
                    if (typeAlreadyIncluded) { continue; }

                    // If this accelerator type is not included in the mapping entry, add it in.
                    // This may happen when an accelerator name happens to be the short name of a different type (rare case).
                    entry.Completions.Add(typeCompletionInstance);
                }
                else
                {
                    entries.Add(type.Key, new TypeCompletionMapping { Key = type.Key, Completions = { typeCompletionInstance } });
                }

                // If the full type name has already been included, then we know for sure that the short type name has also been included.
                string fullTypeName = type.Value.FullName;
                if (entries.ContainsKey(fullTypeName)) { continue; }

                // Otherwise, add the mapping from full type name to the type
                entries.Add(fullTypeName, new TypeCompletionMapping { Key = fullTypeName, Completions = { typeCompletionInstance } });

                // If the short type name is the same as the accelerator name, then skip it to avoid duplication.
                string shortTypeName = type.Value.Name;
                if (type.Key.Equals(shortTypeName, StringComparison.OrdinalIgnoreCase)) { continue; }

                // Otherwise, add a new mapping entry, or put the TypeCompletion instance in the existing mapping entry.
                // For example, this may happen if both System.TimeoutException and System.ServiceProcess.TimeoutException
                // are in the TypeAccelerator cache.
                if (!entries.TryGetValue(shortTypeName, out entry))
                {
                    entry = new TypeCompletionMapping { Key = shortTypeName };
                    entries.Add(shortTypeName, entry);
                }

                entry.Completions.Add(typeCompletionInstance);
            }

            #endregion Process_TypeAccelerators

            #region Process_CoreCLR_TypeCatalog

            // In CoreCLR, we have namespace-qualified type names of all available .NET Core types stored in TypeCatalog.
            // Populate the type completion cache using the namespace-qualified type names.
            foreach (string fullTypeName in ClrFacade.AvailableDotNetTypeNames)
            {
                var typeCompInString = new TypeCompletionInStringFormat { FullTypeName = fullTypeName };
                HandleNamespace(entries, typeCompInString.Namespace);
                HandleType(entries, fullTypeName, typeCompInString.ShortTypeName, null);
            }

            #endregion Process_CoreCLR_TypeCatalog

            #region Process_LoadedAssemblies

            foreach (Assembly assembly in ClrFacade.GetAssemblies())
            {
                // Ignore the assemblies that are already covered by the type catalog
                if (ClrFacade.AvailableDotNetAssemblyNames.Contains(assembly.FullName)) { continue; }

                try
                {
                    foreach (Type type in assembly.GetTypes())
                    {
                        // Ignore non-public types
                        if (!TypeResolver.IsPublic(type)) { continue; }

                        HandleNamespace(entries, type.Namespace);
                        HandleType(entries, type.FullName, type.Name, type);
                    }
                }
                catch (ReflectionTypeLoadException)
                {
                }
            }

            #endregion Process_LoadedAssemblies

            var grouping = entries.Values.GroupBy(t => t.Key.Count(c => c == '.')).OrderBy(g => g.Key).ToArray();
            var localTypeCache = new TypeCompletionMapping[grouping.Last().Key + 1][];
            foreach (var group in grouping)
            {
                localTypeCache[group.Key] = group.ToArray();
            }

            Interlocked.Exchange(ref s_typeCache, localTypeCache);
            return localTypeCache;
        }

        /// <summary>
        /// Handle namespace when initializing the type cache.
        /// </summary>
        /// <param name="entryCache">The TypeCompletionMapping dictionary.</param>
        /// <param name="namespace">The namespace.</param>
        private static void HandleNamespace(Dictionary<string, TypeCompletionMapping> entryCache, string @namespace)
        {
            if (string.IsNullOrEmpty(@namespace))
            {
                return;
            }

            int dotIndex = 0;
            while (dotIndex != -1)
            {
                dotIndex = @namespace.IndexOf('.', dotIndex + 1);
                string subNamespace = dotIndex != -1
                                        ? @namespace.Substring(0, dotIndex)
                                        : @namespace;

                TypeCompletionMapping entry;
                if (!entryCache.TryGetValue(subNamespace, out entry))
                {
                    entry = new TypeCompletionMapping
                    {
                        Key = subNamespace,
                        Completions = { new NamespaceCompletion { Namespace = subNamespace } }
                    };
                    entryCache.Add(subNamespace, entry);
                }
                else if (!entry.Completions.OfType<NamespaceCompletion>().Any())
                {
                    entry.Completions.Add(new NamespaceCompletion { Namespace = subNamespace });
                }
            }
        }

        /// <summary>
        /// Handle a type when initializing the type cache.
        /// </summary>
        /// <param name="entryCache">The TypeCompletionMapping dictionary.</param>
        /// <param name="fullTypeName">The full type name.</param>
        /// <param name="shortTypeName">The short type name.</param>
        /// <param name="actualType">The actual type object. It may be null if we are handling type information from the CoreCLR TypeCatalog.</param>
        private static void HandleType(Dictionary<string, TypeCompletionMapping> entryCache, string fullTypeName, string shortTypeName, Type actualType)
        {
            if (string.IsNullOrEmpty(fullTypeName)) { return; }

            TypeCompletionBase typeCompletionBase = null;
            var backtick = fullTypeName.LastIndexOf('`');
            var plusChar = fullTypeName.LastIndexOf('+');

            bool isGenericTypeDefinition = backtick != -1;
            bool isNested = plusChar != -1;

            if (isGenericTypeDefinition)
            {
                // Nested generic types aren't useful for completion.
                if (isNested) { return; }

                typeCompletionBase = actualType != null
                                         ? (TypeCompletionBase)new GenericTypeCompletion { Type = actualType }

                                         : new GenericTypeCompletionInStringFormat { FullTypeName = fullTypeName };

                // Remove the backtick, we only want 1 generic in our results for types like Func or Action.
                fullTypeName = fullTypeName.Substring(0, backtick);
                shortTypeName = shortTypeName.Substring(0, shortTypeName.LastIndexOf('`'));
            }
            else
            {
                typeCompletionBase = actualType != null
                                         ? (TypeCompletionBase)new TypeCompletion { Type = actualType }

                                         : new TypeCompletionInStringFormat { FullTypeName = fullTypeName };
            }

            // If the full type name has already been included, then we know for sure that the short type
            // name and the accelerator type names (if there are any) have also been included.
            TypeCompletionMapping entry;
            if (!entryCache.TryGetValue(fullTypeName, out entry))
            {
                entry = new TypeCompletionMapping
                {
                    Key = fullTypeName,
                    Completions = { typeCompletionBase }
                };
                entryCache.Add(fullTypeName, entry);

                // Add a new mapping entry, or put the TypeCompletion instance in the existing mapping entry of the shortTypeName.
                // For example, this may happen to System.ServiceProcess.TimeoutException when System.TimeoutException is already in the cache.
                if (!entryCache.TryGetValue(shortTypeName, out entry))
                {
                    entry = new TypeCompletionMapping { Key = shortTypeName };
                    entryCache.Add(shortTypeName, entry);
                }

                entry.Completions.Add(typeCompletionBase);
            }
        }

        internal static List<CompletionResult> CompleteNamespace(CompletionContext context, string prefix = "", string suffix = "")
        {
            var localTypeCache = s_typeCache ?? InitializeTypeCache();
            var results = new List<CompletionResult>();
            var wordToComplete = context.WordToComplete;
            var dots = wordToComplete.Count(c => c == '.');
            if (dots >= localTypeCache.Length || localTypeCache[dots] == null)
            {
                return results;
            }

            var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);

            foreach (var entry in localTypeCache[dots].Where(e => e.Completions.OfType<NamespaceCompletion>().Any() && pattern.IsMatch(e.Key)))
            {
                foreach (var completion in entry.Completions)
                {
                    results.Add(completion.GetCompletionResult(entry.Key, prefix, suffix));
                }
            }

            results.Sort((c1, c2) => string.Compare(c1.ListItemText, c2.ListItemText, StringComparison.OrdinalIgnoreCase));
            return results;
        }

        /// <summary>
        /// Complete a typename.
        /// </summary>
        /// <param name="typeName"></param>
        /// <returns></returns>
        public static IEnumerable<CompletionResult> CompleteType(string typeName)
        {
            // When completing types, we don't care about the runspace, types are visible across the appdomain
            var powershell = (Runspace.DefaultRunspace == null)
                                 ? PowerShell.Create()
                                 : PowerShell.Create(RunspaceMode.CurrentRunspace);

            var helper = new PowerShellExecutionHelper(powershell);
            var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext;
            return CompleteType(new CompletionContext { WordToComplete = typeName, Helper = helper, ExecutionContext = executionContext });
        }

        internal static List<CompletionResult> CompleteType(CompletionContext context, string prefix = "", string suffix = "")
        {
            var localTypeCache = s_typeCache ?? InitializeTypeCache();

            var results = new List<CompletionResult>();
            var completionTextSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
            var wordToComplete = context.WordToComplete;
            var dots = wordToComplete.Count(c => c == '.');
            if (dots >= localTypeCache.Length || localTypeCache[dots] == null)
            {
                return results;
            }

            var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);

            foreach (var entry in localTypeCache[dots].Where(e => pattern.IsMatch(e.Key)))
            {
                foreach (var completion in entry.Completions)
                {
                    string namespaceToRemove = GetNamespaceToRemove(context, completion);
                    var completionResult = completion.GetCompletionResult(entry.Key, prefix, suffix, namespaceToRemove);

                    // We might get the same completion result twice. For example, the type cache has:
                    //    DscResource->System.Management.Automation.DscResourceAttribute (from accelerator)
                    //    DscResourceAttribute->System.Management.Automation.DscResourceAttribute (from short type name)
                    // input '[DSCRes' can match both of them, but they actually resolves to the same completion text 'DscResource'.
                    if (!completionTextSet.Contains(completionResult.CompletionText))
                    {
                        results.Add(completionResult);
                        completionTextSet.Add(completionResult.CompletionText);
                    }
                }
            }

            // this is a temporary fix. Only the type defined in the same script get complete. Need to use using Module when that is available.
            if (context.RelatedAsts != null && context.RelatedAsts.Count > 0)
            {
                var scriptBlockAst = (ScriptBlockAst)context.RelatedAsts[0];
                var typeAsts = scriptBlockAst.FindAll(ast => ast is TypeDefinitionAst, false).Cast<TypeDefinitionAst>();
                foreach (var typeAst in typeAsts.Where(ast => pattern.IsMatch(ast.Name)))
                {
                    string toolTipPrefix = string.Empty;
                    if (typeAst.IsInterface)
                        toolTipPrefix = "Interface ";
                    else if (typeAst.IsClass)
                        toolTipPrefix = "Class ";
                    else if (typeAst.IsEnum)
                        toolTipPrefix = "Enum ";

                    results.Add(new CompletionResult(prefix + typeAst.Name + suffix, typeAst.Name, CompletionResultType.Type, toolTipPrefix + typeAst.Name));
                }
            }

            results.Sort((c1, c2) => string.Compare(c1.ListItemText, c2.ListItemText, StringComparison.OrdinalIgnoreCase));
            return results;
        }

        private static string GetNamespaceToRemove(CompletionContext context, TypeCompletionBase completion)
        {
            if (completion is NamespaceCompletion || context.RelatedAsts == null || context.RelatedAsts.Count == 0)
            {
                return null;
            }

            var typeCompletion = completion as TypeCompletion;
            string typeNameSpace = typeCompletion != null
                                       ? typeCompletion.Type.Namespace
                                       : ((TypeCompletionInStringFormat)completion).Namespace;

            var scriptBlockAst = (ScriptBlockAst)context.RelatedAsts[0];
            var matchingNsStates = scriptBlockAst.UsingStatements.Where(s =>
                 s.UsingStatementKind == UsingStatementKind.Namespace
                 && typeNameSpace != null
                 && typeNameSpace.StartsWith(s.Name.Value, StringComparison.OrdinalIgnoreCase));

            string ns = string.Empty;
            foreach (var nsState in matchingNsStates)
            {
                if (nsState.Name.Extent.Text.Length > ns.Length)
                {
                    ns = nsState.Name.Extent.Text;
                }
            }

            return ns;
        }

        #endregion Types

        #region Help Topics

        internal static List<CompletionResult> CompleteHelpTopics(CompletionContext context)
        {
            var results = new List<CompletionResult>();
            var searchPaths = new List<string>();
            var currentCulture = CultureInfo.CurrentCulture.Name;

            // Add the user scope path first, since it is searched in order.
            var userHelpRoot = Path.Combine(HelpUtils.GetUserHomeHelpSearchPath(), currentCulture);

            if (Directory.Exists(userHelpRoot))
            {
                searchPaths.Add(userHelpRoot);
            }

            var dirPath = Path.Combine(Utils.GetApplicationBase(Utils.DefaultPowerShellShellID), currentCulture);
            searchPaths.Add(dirPath);

            var wordToComplete = context.WordToComplete + "*";
            var topicPattern = WildcardPattern.Get("about_*.help.txt", WildcardOptions.IgnoreCase);
            List<string> files = new List<string>();

            try
            {
                var wildcardPattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase);

                foreach (var dir in searchPaths)
                {
                    foreach (var file in Directory.GetFiles(dir))
                    {
                        if (wildcardPattern.IsMatch(Path.GetFileName(file)))
                        {
                            files.Add(file);
                        }
                    }
                }
            }
            catch (Exception)
            {
            }

            if (files != null)
            {
                foreach (string file in files)
                {
                    if (file == null)
                    {
                        continue;
                    }

                    try
                    {
                        var fileName = Path.GetFileName(file);
                        if (fileName == null || !topicPattern.IsMatch(fileName))
                            continue;

                        // All topic files are ending with ".help.txt"
                        var completionText = fileName.Substring(0, fileName.Length - 9);
                        results.Add(new CompletionResult(completionText));
                    }
                    catch (Exception)
                    {
                        continue;
                    }
                }
            }

            return results;
        }

        #endregion Help Topics

        #region Statement Parameters

        internal static List<CompletionResult> CompleteStatementFlags(TokenKind kind, string wordToComplete)
        {
            switch (kind)
            {
                case TokenKind.Switch:

                    Diagnostics.Assert(!string.IsNullOrEmpty(wordToComplete) && wordToComplete[0].IsDash(), "the word to complete should start with '-'");
                    wordToComplete = wordToComplete.Substring(1);
                    bool withColon = wordToComplete.EndsWith(':');
                    wordToComplete = withColon ? wordToComplete.Remove(wordToComplete.Length - 1) : wordToComplete;

                    string enumString = LanguagePrimitives.EnumSingleTypeConverter.EnumValues(typeof(SwitchFlags));
                    string separator = CultureInfo.CurrentUICulture.TextInfo.ListSeparator;
                    string[] enumArray = enumString.Split(separator, StringSplitOptions.RemoveEmptyEntries);

                    var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase);
                    var enumList = new List<string>();
                    var result = new List<CompletionResult>();
                    CompletionResult fullMatch = null;

                    foreach (string value in enumArray)
                    {
                        if (value.Equals(SwitchFlags.None.ToString(), StringComparison.OrdinalIgnoreCase)) { continue; }

                        if (wordToComplete.Equals(value, StringComparison.OrdinalIgnoreCase))
                        {
                            string completionText = withColon ? "-" + value + ":" : "-" + value;
                            fullMatch = new CompletionResult(completionText, value, CompletionResultType.ParameterName, value);
                            continue;
                        }

                        if (pattern.IsMatch(value))
                        {
                            enumList.Add(value);
                        }
                    }

                    if (fullMatch != null)
                    {
                        result.Add(fullMatch);
                    }

                    enumList.Sort();
                    result.AddRange(from entry in enumList
                                    let completionText = withColon ? "-" + entry + ":" : "-" + entry
                                    select new CompletionResult(completionText, entry, CompletionResultType.ParameterName, entry));

                    return result;

                default:
                    break;
            }

            return null;
        }

        #endregion Statement Parameters

        #region Hashtable Keys

        /// <summary>
        /// Generate auto complete results for hashtable key within a Dynamickeyword.
        /// Results are generated based on properties of a DynamicKeyword matches given identifier.
        /// For example, following "D" matches "DestinationPath"
        ///
        ///     Configuration
        ///     {
        ///         File
        ///         {
        ///             D^
        ///         }
        ///     }
        /// </summary>
        /// <param name="completionContext"></param>
        /// <param name="ast"></param>
        /// <param name="hashtableAst"></param>
        /// <returns></returns>
        internal static List<CompletionResult> CompleteHashtableKeyForDynamicKeyword(
            CompletionContext completionContext,
            DynamicKeywordStatementAst ast,
            HashtableAst hashtableAst)
        {
            Diagnostics.Assert(ast.Keyword != null, "DynamicKeywordStatementAst.Keyword can never be null");
            List<CompletionResult> results = null;
            var dynamicKeywordProperties = ast.Keyword.Properties;
            var memberPattern = completionContext.WordToComplete + "*";

            //
            // Capture all existing properties in hashtable
            //
            var propertiesName = new List<string>();
            int cursorOffset = completionContext.CursorPosition.Offset;
            foreach (var keyValueTuple in hashtableAst.KeyValuePairs)
            {
                var propName = keyValueTuple.Item1 as StringConstantExpressionAst;
                // Exclude the property name at cursor
                if (propName != null && propName.Extent.EndOffset != cursorOffset)
                {
                    propertiesName.Add(propName.Value);
                }
            }

            if (dynamicKeywordProperties.Count > 0)
            {
                // Excludes existing properties in the hashtable statement
                var tempProperties = dynamicKeywordProperties.Where(p => !propertiesName.Contains(p.Key, StringComparer.OrdinalIgnoreCase));
                if (tempProperties != null && tempProperties.Any())
                {
                    results = new List<CompletionResult>();
                    // Filter by name
                    var wildcardPattern = WildcardPattern.Get(memberPattern, WildcardOptions.IgnoreCase);
                    var matchedResults = tempProperties.Where(p => wildcardPattern.IsMatch(p.Key));
                    if (matchedResults == null || !matchedResults.Any())
                    {
                        // Fallback to all non-exist properties in the hashtable statement
                        matchedResults = tempProperties;
                    }

                    foreach (var p in matchedResults)
                    {
                        string psTypeName = LanguagePrimitives.ConvertTypeNameToPSTypeName(p.Value.TypeConstraint);
                        if (psTypeName == "[]" || string.IsNullOrEmpty(psTypeName))
                        {
                            psTypeName = "[" + p.Value.TypeConstraint + "]";
                        }

                        if (string.Equals(psTypeName, "[MSFT_Credential]", StringComparison.OrdinalIgnoreCase))
                        {
                            psTypeName = "[pscredential]";
                        }

                        results.Add(new CompletionResult(
                            p.Key + " = ",
                            p.Key,
                            CompletionResultType.Property,
                            psTypeName));
                    }
                }
            }

            return results;
        }

        internal static List<CompletionResult> CompleteHashtableKey(CompletionContext completionContext, HashtableAst hashtableAst)
        {
            var typeAst = hashtableAst.Parent as ConvertExpressionAst;
            if (typeAst != null)
            {
                var result = new List<CompletionResult>();
                CompleteMemberByInferredType(
                    completionContext.TypeInferenceContext, AstTypeInference.InferTypeOf(typeAst, completionContext.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval),
                    result, completionContext.WordToComplete + "*", IsWriteablePropertyMember, isStatic: false);
                return result;
            }

            // hashtable arguments sometimes have expected keys.  Examples:
            //   new-object System.Drawing.Point -prop @{ X=1; Y=1 }
            //   dir | sort-object -prop @{Expression=...   ; Ascending=... }
            // format-table -Property
            //     Expression
            //     FormatString
            //     Label
            //     Width
            //     Alignment
            // format-list -Property
            //     Expression
            //     FormatString
            //     Label
            // format-custom -Property
            //     Expression
            //     Depth
            // format-* -GroupBy
            //     Expression
            //     FormatString
            //     Label
            //

            // Find out if we are in a command argument.  Consider the following possibilities:
            //     cmd @{}
            //     cmd -foo @{}
            //     cmd -foo:@{}
            //     cmd @{},@{}
            //     cmd -foo @{},@{}
            //     cmd -foo:@{},@{}

            var ast = hashtableAst.Parent;

            // Handle completion for hashtable within DynamicKeyword statement
            var dynamicKeywordStatementAst = ast as DynamicKeywordStatementAst;
            if (dynamicKeywordStatementAst != null)
            {
                return CompleteHashtableKeyForDynamicKeyword(completionContext, dynamicKeywordStatementAst, hashtableAst);
            }

            if (ast is ArrayLiteralAst)
            {
                ast = ast.Parent;
            }

            if (ast is CommandParameterAst)
            {
                ast = ast.Parent;
            }

            var commandAst = ast as CommandAst;
            if (commandAst != null)
            {
                var binding = new PseudoParameterBinder().DoPseudoParameterBinding(commandAst, null, null, bindingType: PseudoParameterBinder.BindingType.ArgumentCompletion);
                if (binding == null)
                {
                    return null;
                }

                string parameterName = null;
                foreach (var boundArg in binding.BoundArguments)
                {
                    var astPair = boundArg.Value as AstPair;
                    if (astPair != null)
                    {
                        if (astPair.Argument == hashtableAst)
                        {
                            parameterName = boundArg.Key;
                            break;
                        }

                        continue;
                    }

                    var astArrayPair = boundArg.Value as AstArrayPair;
                    if (astArrayPair != null)
                    {
                        if (astArrayPair.Argument.Contains(hashtableAst))
                        {
                            parameterName = boundArg.Key;
                            break;
                        }

                        continue;
                    }
                }

                if (parameterName != null)
                {
                    if (parameterName.Equals("GroupBy", StringComparison.OrdinalIgnoreCase))
                    {
                        switch (binding.CommandName)
                        {
                            case "Format-Table":
                            case "Format-List":
                            case "Format-Wide":
                            case "Format-Custom":
                                return GetSpecialHashTableKeyMembers("Expression", "FormatString", "Label");
                        }

                        return null;
                    }

                    if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase))
                    {
                        switch (binding.CommandName)
                        {
                            case "New-Object":
                                var inferredType = AstTypeInference.InferTypeOf(commandAst, completionContext.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval);
                                var result = new List<CompletionResult>();
                                CompleteMemberByInferredType(
                                    completionContext.TypeInferenceContext, inferredType,
                                    result, completionContext.WordToComplete + "*", IsWriteablePropertyMember, isStatic: false);
                                return result;
                            case "Select-Object":
                                return GetSpecialHashTableKeyMembers("Name", "Expression");
                            case "Sort-Object":
                                return GetSpecialHashTableKeyMembers("Expression", "Ascending", "Descending");
                            case "Group-Object":
                                return GetSpecialHashTableKeyMembers("Expression");
                            case "Format-Table":
                                return GetSpecialHashTableKeyMembers("Expression", "FormatString", "Label", "Width", "Alignment");
                            case "Format-List":
                                return GetSpecialHashTableKeyMembers("Expression", "FormatString", "Label");
                            case "Format-Wide":
                                return GetSpecialHashTableKeyMembers("Expression", "FormatString");
                            case "Format-Custom":
                                return GetSpecialHashTableKeyMembers("Expression", "Depth");
                        }
                    }
                }
            }

            return null;
        }

        private static List<CompletionResult> GetSpecialHashTableKeyMembers(params string[] keys)
        {
            // Resources were removed because they missed the deadline for loc.
            // return keys.Select(key => new CompletionResult(key, key, CompletionResultType.Property,
            //    ResourceManagerCache.GetResourceString(typeof(CompletionCompleters).Assembly,
            //                                           "TabCompletionStrings", key + "HashKeyDescription"))).ToList();
            return keys.Select(key => new CompletionResult(key, key, CompletionResultType.Property, key)).ToList();
        }

        #endregion Hashtable Keys

        #region Helpers

        internal static bool IsPathSafelyExpandable(ExpandableStringExpressionAst expandableStringAst, string extraText, ExecutionContext executionContext, out string expandedString)
        {
            expandedString = null;
            // Expand the string if its type is DoubleQuoted or BareWord
            var constType = expandableStringAst.StringConstantType;
            if (constType == StringConstantType.DoubleQuotedHereString) { return false; }

            Diagnostics.Assert(
                constType == StringConstantType.BareWord ||
                (constType == StringConstantType.DoubleQuoted && expandableStringAst.Extent.Text[0].IsDoubleQuote()),
                "the string to be expanded should be either BareWord or DoubleQuoted");

            var varValues = new List<string>();
            foreach (ExpressionAst nestedAst in expandableStringAst.NestedExpressions)
            {
                var variableAst = nestedAst as VariableExpressionAst;
                if (variableAst == null) { return false; }

                string strValue = CombineVariableWithPartialPath(variableAst, null, executionContext);
                if (strValue != null)
                {
                    varValues.Add(strValue);
                }
                else
                {
                    return false;
                }
            }

            var formattedString = string.Format(CultureInfo.InvariantCulture, expandableStringAst.FormatExpression, varValues.ToArray());
            string quote = (constType == StringConstantType.DoubleQuoted) ? "\"" : string.Empty;

            expandedString = quote + formattedString + extraText + quote;
            return true;
        }

        internal static string CombineVariableWithPartialPath(VariableExpressionAst variableAst, string extraText, ExecutionContext executionContext)
        {
            var varPath = variableAst.VariablePath;
            if (varPath.IsVariable || varPath.DriveName.Equals("env", StringComparison.OrdinalIgnoreCase))
            {
                try
                {
                    // We check the strict mode inside GetVariableValue
                    object value = VariableOps.GetVariableValue(varPath, executionContext, variableAst);
                    var strValue = (value == null) ? string.Empty : value as string;

                    if (strValue == null)
                    {
                        object baseObj = PSObject.Base(value);
                        if (baseObj is string || baseObj.GetType().IsPrimitive)
                        {
                            strValue = LanguagePrimitives.ConvertTo<string>(value);
                        }
                    }

                    if (strValue != null)
                    {
                        return strValue + extraText;
                    }
                }
                catch (Exception)
                {
                }
            }

            return null;
        }

        internal static string HandleDoubleAndSingleQuote(ref string wordToComplete)
        {
            string quote = string.Empty;

            if (!string.IsNullOrEmpty(wordToComplete) && (wordToComplete[0].IsSingleQuote() || wordToComplete[0].IsDoubleQuote()))
            {
                char frontQuote = wordToComplete[0];
                int length = wordToComplete.Length;

                if (length == 1)
                {
                    wordToComplete = string.Empty;
                    quote = frontQuote.IsSingleQuote() ? "'" : "\"";
                }
                else if (length > 1)
                {
                    if ((wordToComplete[length - 1].IsDoubleQuote() && frontQuote.IsDoubleQuote()) || (wordToComplete[length - 1].IsSingleQuote() && frontQuote.IsSingleQuote()))
                    {
                        wordToComplete = wordToComplete.Substring(1, length - 2);
                        quote = frontQuote.IsSingleQuote() ? "'" : "\"";
                    }
                    else if (!wordToComplete[length - 1].IsDoubleQuote() && !wordToComplete[length - 1].IsSingleQuote())
                    {
                        wordToComplete = wordToComplete.Substring(1);
                        quote = frontQuote.IsSingleQuote() ? "'" : "\"";
                    }
                }
            }

            return quote;
        }

        internal static bool IsSplattedVariable(Ast targetExpr)
        {
            if (targetExpr is VariableExpressionAst && ((VariableExpressionAst)targetExpr).Splatted)
            {
                // It's splatted variable, member expansion is not useful
                return true;
            }

            return false;
        }

        internal static void CompleteMemberHelper(
            bool @static,
            string memberName,
            ExpressionAst targetExpr,
            CompletionContext context,
            List<CompletionResult> results)
        {
            object value;
            if (SafeExprEvaluator.TrySafeEval(targetExpr, context.ExecutionContext, out value) && value != null)
            {
                if (targetExpr is ArrayExpressionAst && !(value is object[]))
                {
                    // When the array contains only one element, the evaluation result would be that element. We wrap it into an array
                    value = new[] { value };
                }

                // Instead of Get-Member, we access the members directly and send as input to the pipe.
                var powerShellExecutionHelper = context.Helper;
                powerShellExecutionHelper
                    .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Core\\Where-Object")
                    .AddParameter("Property", "Name")
                    .AddParameter("Like")
                    .AddParameter("Value", memberName)
                    .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object")
                    .AddParameter("Property", new object[] { "MemberType", "Name" });

                IEnumerable members;
                if (@static)
                {
                    var type = PSObject.Base(value) as Type;
                    if (type == null)
                    {
                        return;
                    }

                    members = PSObject.DotNetStaticAdapter.BaseGetMembers<PSMemberInfo>(type);
                }
                else
                {
                    members = PSObject.AsPSObject(value).Members;
                }

                var sortedMembers = powerShellExecutionHelper.ExecuteCurrentPowerShell(out _, members);

                foreach (var member in sortedMembers)
                {
                    var memberInfo = (PSMemberInfo)PSObject.Base(member);
                    if (memberInfo.IsHidden)
                    {
                        continue;
                    }

                    var completionText = memberInfo.Name;

                    // Handle scenarios like this: $aa | add-member 'a b' 23; $aa.a<tab>
                    if (completionText.IndexOfAny(s_charactersRequiringQuotes) != -1)
                    {
                        completionText = completionText.Replace("'", "''");
                        completionText = "'" + completionText + "'";
                    }

                    var isMethod = memberInfo is PSMethodInfo;
                    if (isMethod)
                    {
                        var isSpecial = (memberInfo is PSMethod) && ((PSMethod)memberInfo).IsSpecial;
                        if (isSpecial)
                            continue;
                        completionText += '(';
                    }

                    string tooltip = memberInfo.ToString();
                    if (tooltip.Contains("),", StringComparison.Ordinal))
                    {
                        var overloads = tooltip.Split("),", StringSplitOptions.RemoveEmptyEntries);
                        var newTooltip = new StringBuilder();
                        foreach (var overload in overloads)
                        {
                            newTooltip.Append(overload.Trim() + ")\r\n");
                        }

                        newTooltip.Remove(newTooltip.Length - 3, 3);
                        tooltip = newTooltip.ToString();
                    }

                    results.Add(
                        new CompletionResult(completionText, memberInfo.Name,
                                             isMethod ? CompletionResultType.Method : CompletionResultType.Property,
                                             tooltip));
                }

                var dictionary = PSObject.Base(value) as IDictionary;
                if (dictionary != null)
                {
                    var pattern = WildcardPattern.Get(memberName, WildcardOptions.IgnoreCase);
                    foreach (DictionaryEntry entry in dictionary)
                    {
                        var key = entry.Key as string;
                        if (key == null)
                            continue;

                        if (pattern.IsMatch(key))
                        {
                            // Handle scenarios like this: $hashtable["abc#d"] = 100; $hashtable.ab<tab>
                            if (key.IndexOfAny(s_charactersRequiringQuotes) != -1)
                            {
                                key = key.Replace("'", "''");
                                key = "'" + key + "'";
                            }

                            results.Add(new CompletionResult(key, key, CompletionResultType.Property, key));
                        }
                    }
                }

                if (!@static && IsValueEnumerable(PSObject.Base(value)))
                {
                    // Complete extension methods 'Where' and 'ForEach' for Enumerable values
                    CompleteExtensionMethods(memberName, results);
                }
            }
        }

        /// <summary>
        /// Check if a value is treated as Enumerable in powershell.
        /// </summary>
        private static bool IsValueEnumerable(object value)
        {
            object baseValue = PSObject.Base(value);

            if (baseValue == null || baseValue is string || baseValue is PSObject ||
                baseValue is IDictionary || baseValue is System.Xml.XmlNode)
            {
                return false;
            }

            if (baseValue is IEnumerable || baseValue is IEnumerator || baseValue is DataTable)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Check if a strong type is treated as Enumerable in powershell.
        /// </summary>
        private static bool IsStaticTypeEnumerable(Type type)
        {
            if (type.Equals(typeof(string)) || typeof(IDictionary).IsAssignableFrom(type) || typeof(System.Xml.XmlNode).IsAssignableFrom(type))
            {
                return false;
            }

            if (typeof(IEnumerable).IsAssignableFrom(type) || typeof(IEnumerator).IsAssignableFrom(type))
            {
                return true;
            }

            return false;
        }

        private static bool CompletionRequiresQuotes(string completion, bool escape)
        {
            // If the tokenizer sees the completion as more than two tokens, or if there is some error, then
            // some form of quoting is necessary (if it's a variable, we'd need ${}, filenames would need [], etc.)

            Language.Token[] tokens;
            ParseError[] errors;
            Language.Parser.ParseInput(completion, out tokens, out errors);

            char[] charToCheck = escape ? new[] { '$', '[', ']', '`' } : new[] { '$', '`' };

            // Expect no errors and 2 tokens (1 is for our completion, the other is eof)
            // Or if the completion is a keyword, we ignore the errors
            bool requireQuote = !(errors.Length == 0 && tokens.Length == 2);
            if ((!requireQuote && tokens[0] is StringToken) ||
                (tokens.Length == 2 && (tokens[0].TokenFlags & TokenFlags.Keyword) != 0))
            {
                requireQuote = false;
                var value = tokens[0].Text;
                if (value.IndexOfAny(charToCheck) != -1)
                    requireQuote = true;
            }

            return requireQuote;
        }

        private static bool ProviderSpecified(string path)
        {
            var index = path.IndexOf(':');
            return index != -1 && index + 1 < path.Length && path[index + 1] == ':';
        }

        private static Type GetEffectiveParameterType(Type type)
        {
            var underlying = Nullable.GetUnderlyingType(type);
            return underlying ?? type;
        }

        /// <summary>
        /// Turn on the "LiteralPaths" option.
        /// </summary>
        /// <param name="completionContext"></param>
        /// <returns>
        /// Indicate whether the "LiteralPaths" option needs to be removed after operation
        /// </returns>
        private static bool TurnOnLiteralPathOption(CompletionContext completionContext)
        {
            bool clearLiteralPathsKey = false;

            if (completionContext.Options == null)
            {
                completionContext.Options = new Hashtable { { "LiteralPaths", true } };
                clearLiteralPathsKey = true;
            }
            else if (!completionContext.Options.ContainsKey("LiteralPaths"))
            {
                // Dont escape '[',']','`' when the file name is treated as command name
                completionContext.Options.Add("LiteralPaths", true);
                clearLiteralPathsKey = true;
            }

            return clearLiteralPathsKey;
        }

        /// <summary>
        /// Return whether we need to add ampersand when it's necessary.
        /// </summary>
        /// <param name="context"></param>
        /// <param name="defaultChoice"></param>
        /// <returns></returns>
        internal static bool IsAmpersandNeeded(CompletionContext context, bool defaultChoice)
        {
            if (context.RelatedAsts != null && !string.IsNullOrEmpty(context.WordToComplete))
            {
                var lastAst = context.RelatedAsts.Last();
                var parent = lastAst.Parent as CommandAst;

                if (parent != null && parent.CommandElements.Count == 1 &&
                    ((!defaultChoice && parent.InvocationOperator == TokenKind.Unknown) ||
                     (defaultChoice && parent.InvocationOperator != TokenKind.Unknown)))
                {
                    // - When the default choice is NOT to add ampersand, we only return true
                    //   when the invocation operator is NOT specified.
                    // - When the default choice is to add ampersand, we only return false
                    //   when the invocation operator is specified.
                    defaultChoice = !defaultChoice;
                }
            }

            return defaultChoice;
        }

        private class ItemPathComparer : IComparer<PSObject>
        {
            public int Compare(PSObject x, PSObject y)
            {
                var xPathInfo = PSObject.Base(x) as PathInfo;
                var xFileInfo = PSObject.Base(x) as IO.FileSystemInfo;
                var xPathStr = PSObject.Base(x) as string;

                var yPathInfo = PSObject.Base(y) as PathInfo;
                var yFileInfo = PSObject.Base(y) as IO.FileSystemInfo;
                var yPathStr = PSObject.Base(y) as string;

                string xPath = null, yPath = null;

                if (xPathInfo != null)
                    xPath = xPathInfo.ProviderPath;
                else if (xFileInfo != null)
                    xPath = xFileInfo.FullName;
                else if (xPathStr != null)
                    xPath = xPathStr;

                if (yPathInfo != null)
                    yPath = yPathInfo.ProviderPath;
                else if (yFileInfo != null)
                    yPath = yFileInfo.FullName;
                else if (yPathStr != null)
                    yPath = yPathStr;

                if (string.IsNullOrEmpty(xPath) || string.IsNullOrEmpty(yPath))
                    Diagnostics.Assert(false, "Base object of item PSObject should be either PathInfo or FileSystemInfo");

                return string.Compare(xPath, yPath, StringComparison.CurrentCultureIgnoreCase);
            }
        }

        private class CommandNameComparer : IComparer<PSObject>
        {
            public int Compare(PSObject x, PSObject y)
            {
                string xName = null;
                string yName = null;

                object xObj = PSObject.Base(x);
                object yObj = PSObject.Base(y);

                var xCommandInfo = xObj as CommandInfo;
                xName = xCommandInfo != null ? xCommandInfo.Name : xObj as string;

                var yCommandInfo = yObj as CommandInfo;
                yName = yCommandInfo != null ? yCommandInfo.Name : yObj as string;

                if (xName == null || yName == null)
                    Diagnostics.Assert(false, "Base object of Command PSObject should be either CommandInfo or string");

                return string.Compare(xName, yName, StringComparison.OrdinalIgnoreCase);
            }
        }

        #endregion Helpers
    }

    /// <summary>
    /// This class is very similar to the restricted langauge checker, but it is meant to allow more things, yet still
    /// be considered "safe", at least in the sense that tab completion can rely on it to not do bad things.  The primary
    /// use is for intellisense where you don't want to run arbitrary code, but you do want to know the values
    /// of various expressions so you can get the members.
    /// </summary>
    internal class SafeExprEvaluator : ICustomAstVisitor2
    {
        internal static bool TrySafeEval(ExpressionAst ast, ExecutionContext executionContext, out object value)
        {
            if (!(bool)ast.Accept(new SafeExprEvaluator()))
            {
                value = null;
                return false;
            }

            try
            {
                // ConstrainedLanguage has already been applied as necessary when we construct CompletionContext
                Diagnostics.Assert(!(executionContext.HasRunspaceEverUsedConstrainedLanguageMode && executionContext.LanguageMode != PSLanguageMode.ConstrainedLanguage),
                                   "If the runspace has ever used constrained language mode, then the current language mode should already be set to constrained language");

                // We're passing 'true' here for isTrustedInput, because SafeExprEvaluator ensures that the AST
                // has no dangerous side-effects such as arbitrary expression evaluation. It does require variable
                // access and a few other minor things, which staples of tab completion:
                //
                // $t = Get-Process
                // $t[0].MainModule.<TAB>
                //
                value = Compiler.GetExpressionValue(ast, true, executionContext);
                return true;
            }
            catch
            {
                value = null;
                return false;
            }
        }

        public object VisitErrorStatement(ErrorStatementAst errorStatementAst) { return false; }

        public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { return false; }

        public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { return false; }

        public object VisitParamBlock(ParamBlockAst paramBlockAst) { return false; }

        public object VisitNamedBlock(NamedBlockAst namedBlockAst) { return false; }

        public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { return false; }

        public object VisitAttribute(AttributeAst attributeAst) { return false; }

        public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { return false; }

        public object VisitParameter(ParameterAst parameterAst) { return false; }

        public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { return false; }

        public object VisitIfStatement(IfStatementAst ifStmtAst) { return false; }

        public object VisitTrap(TrapStatementAst trapStatementAst) { return false; }

        public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) { return false; }

        public object VisitDataStatement(DataStatementAst dataStatementAst) { return false; }

        public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) { return false; }

        public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { return false; }

        public object VisitForStatement(ForStatementAst forStatementAst) { return false; }

        public object VisitWhileStatement(WhileStatementAst whileStatementAst) { return false; }

        public object VisitCatchClause(CatchClauseAst catchClauseAst) { return false; }

        public object VisitTryStatement(TryStatementAst tryStatementAst) { return false; }

        public object VisitBreakStatement(BreakStatementAst breakStatementAst) { return false; }

        public object VisitContinueStatement(ContinueStatementAst continueStatementAst) { return false; }

        public object VisitReturnStatement(ReturnStatementAst returnStatementAst) { return false; }

        public object VisitExitStatement(ExitStatementAst exitStatementAst) { return false; }

        public object VisitThrowStatement(ThrowStatementAst throwStatementAst) { return false; }

        public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { return false; }

        public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { return false; }
        // REVIEW: we could relax this to allow specific commands
        public object VisitCommand(CommandAst commandAst) { return false; }

        public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) { return false; }

        public object VisitCommandParameter(CommandParameterAst commandParameterAst) { return false; }

        public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) { return false; }

        public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) { return false; }

        public object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) { return false; }

        public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { return false; }

        public object VisitBlockStatement(BlockStatementAst blockStatementAst) { return false; }

        public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { return false; }

        public object VisitUsingExpression(UsingExpressionAst usingExpressionAst) { return false; }

        public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return false; }

        public object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return false; }

        public object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return false; }

        public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return false; }

        public object VisitUsingStatement(UsingStatementAst usingStatementAst) { return false; }

        public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordStatementAst) { return false; }

        public object VisitPipelineChain(PipelineChainAst pipelineChainAst) { return false; }

        public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst)
        {
            return configurationDefinitionAst.Body.Accept(this);
        }

        public object VisitStatementBlock(StatementBlockAst statementBlockAst)
        {
            if (statementBlockAst.Traps != null) return false;
            // REVIEW: we could relax this to allow multiple statements
            if (statementBlockAst.Statements.Count > 1) return false;
            var pipeline = statementBlockAst.Statements.FirstOrDefault();
            return pipeline != null && (bool)pipeline.Accept(this);
        }

        public object VisitPipeline(PipelineAst pipelineAst)
        {
            var expr = pipelineAst.GetPureExpression();
            return expr != null && (bool)expr.Accept(this);
        }

        public object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst)
        {
            return (bool)ternaryExpressionAst.Condition.Accept(this) &&
                   (bool)ternaryExpressionAst.IfTrue.Accept(this) &&
                   (bool)ternaryExpressionAst.IfFalse.Accept(this);
        }

        public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
        {
            return (bool)binaryExpressionAst.Left.Accept(this) && (bool)binaryExpressionAst.Right.Accept(this);
        }

        public object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst)
        {
            return (bool)unaryExpressionAst.Child.Accept(this);
        }

        public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst)
        {
            return (bool)convertExpressionAst.Child.Accept(this);
        }

        public object VisitConstantExpression(ConstantExpressionAst constantExpressionAst)
        {
            return true;
        }

        public object VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst)
        {
            return true;
        }

        public object VisitSubExpression(SubExpressionAst subExpressionAst)
        {
            return subExpressionAst.SubExpression.Accept(this);
        }

        public object VisitVariableExpression(VariableExpressionAst variableExpressionAst)
        {
            return true;
        }

        public object VisitTypeExpression(TypeExpressionAst typeExpressionAst)
        {
            return true;
        }

        public object VisitMemberExpression(MemberExpressionAst memberExpressionAst)
        {
            return (bool)memberExpressionAst.Expression.Accept(this) && (bool)memberExpressionAst.Member.Accept(this);
        }

        public object VisitIndexExpression(IndexExpressionAst indexExpressionAst)
        {
            return (bool)indexExpressionAst.Target.Accept(this) && (bool)indexExpressionAst.Index.Accept(this);
        }

        public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst)
        {
            return arrayExpressionAst.SubExpression.Accept(this);
        }

        public object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst)
        {
            return arrayLiteralAst.Elements.All(e => (bool)e.Accept(this));
        }

        public object VisitHashtable(HashtableAst hashtableAst)
        {
            foreach (var keyValuePair in hashtableAst.KeyValuePairs)
            {
                if (!(bool)keyValuePair.Item1.Accept(this))
                    return false;
                if (!(bool)keyValuePair.Item2.Accept(this))
                    return false;
            }

            return true;
        }

        public object VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)
        {
            return true;
        }

        public object VisitParenExpression(ParenExpressionAst parenExpressionAst)
        {
            return parenExpressionAst.Pipeline.Accept(this);
        }
    }

    /// <summary>
    /// Completes with the property names of the InputObject.
    /// </summary>
    internal class PropertyNameCompleter : IArgumentCompleter
    {
        private readonly string _parameterNameOfInput;

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyNameCompleter"/> class.
        /// </summary>
        public PropertyNameCompleter()
        {
            _parameterNameOfInput = "InputObject";
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyNameCompleter"/> class.
        /// </summary>
        /// <param name="parameterNameOfInput">The name of the property of the input object for witch to complete with property names.</param>
        public PropertyNameCompleter(string parameterNameOfInput)
        {
            _parameterNameOfInput = parameterNameOfInput;
        }

        IEnumerable<CompletionResult> IArgumentCompleter.CompleteArgument(
            string commandName,
            string parameterName,
            string wordToComplete,
            CommandAst commandAst,
            IDictionary fakeBoundParameters)
        {
            if (commandAst.Parent is not PipelineAst pipelineAst)
            {
                return null;
            }

            int i;
            for (i = 0; i < pipelineAst.PipelineElements.Count; i++)
            {
                if (pipelineAst.PipelineElements[i] == commandAst)
                {
                    break;
                }
            }

            var typeInferenceContext = new TypeInferenceContext();
            IEnumerable<PSTypeName> prevType;
            if (i == 0)
            {
                var parameterAst = (CommandParameterAst)commandAst.Find(ast => ast is CommandParameterAst cpa && cpa.ParameterName == "PropertyName", false);
                var pseudoBinding = new PseudoParameterBinder().DoPseudoParameterBinding(commandAst, null, parameterAst, PseudoParameterBinder.BindingType.ParameterCompletion);
                if (!pseudoBinding.BoundArguments.TryGetValue(_parameterNameOfInput, out var pair) || !pair.ArgumentSpecified)
                {
                    return null;
                }

                if (pair is AstPair astPair && astPair.Argument != null)
                {
                    prevType = AstTypeInference.InferTypeOf(astPair.Argument, typeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval);
                }

                return null;
            }
            else
            {
                prevType = AstTypeInference.InferTypeOf(pipelineAst.PipelineElements[i - 1], typeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval);
            }

            var result = new List<CompletionResult>();

            CompletionCompleters.CompleteMemberByInferredType(typeInferenceContext, prevType, result, wordToComplete + "*", filter: CompletionCompleters.IsPropertyMember, isStatic: false);
            return result;
        }
    }
}
