// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.AspNetCore.Razor.Utilities;

namespace System.Collections.Immutable;

/// <summary>
/// <see cref="ImmutableArray{T}"/> extension methods
/// </summary>
internal static partial class ImmutableArrayExtensions
{
    /// <summary>
    /// Returns an empty array if the input array is null (default)
    /// </summary>
    public static ImmutableArray<T> NullToEmpty<T>(this ImmutableArray<T> array)
    {
        return array.IsDefault ? [] : array;
    }

    public static void SetCapacityIfLarger<T>(this ImmutableArray<T>.Builder builder, int newCapacity)
    {
        if (builder.Capacity < newCapacity)
        {
            builder.Capacity = newCapacity;
        }
    }

    public static void InsertRange<T>(this ImmutableArray<T>.Builder builder, int index, ReadOnlySpan<T> items)
    {
        // ImmutableArray<T>.Builder doesn't currently provide an overload of InsertRange(...) that takes
        // a ReadOnlySpan<T>, so we add our own here.

        ArgHelper.ThrowIfNegative(index);
        ArgHelper.ThrowIfGreaterThan(index, builder.Count);

        if (items.Length == 0)
        {
            // No items? Nothing to do.
            return;
        }

        if (index == builder.Count)
        {
            // If we're inserting at the end of the builder, we can call AddRange(...) which *does* provide
            // an overload that takes a ReadOnlySpan<T>.
            builder.AddRange(items);
        }
        else if (items.Length == 1)
        {
            // If our span contains a single item, we can just insert that item.
            builder.Insert(index, items[0]);
        }
        else
        {
            // As a general strategy, we create an ImmutableArray<T> for the ReadOnlySpan<T> and call
            // the InsertRange(...) overload that takes an ImmutableArray<T>. This should be more efficient than
            // calling the overload that takes an IEnumerable<T>.
            var array = ImmutableArray.Create(items);
            builder.InsertRange(index, array);
        }
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/>.
    /// </returns>
    public static ImmutableArray<TResult> SelectAsArray<T, TResult>(this ImmutableArray<T> array, Func<T, TResult> selector)
    {
        var length = array.Length;

        if (length == 0)
        {
            return [];
        }

        var result = new TResult[length];

        for (var i = 0; i < length; i++)
        {
            result[i] = selector(array[i]);
        }

        return ImmutableCollectionsMarshal.AsImmutableArray(result);
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form by incorporating the element's index.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on.</param>
    /// <param name="selector">
    ///  A transform function to apply to each element; the second parameter of the function represents the index of the element.
    /// </param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/>.
    /// </returns>
    public static ImmutableArray<TResult> SelectAsArray<T, TResult>(this ImmutableArray<T> array, Func<T, int, TResult> selector)
    {
        var length = array.Length;

        if (length == 0)
        {
            return [];
        }

        var result = new TResult[length];

        for (var i = 0; i < length; i++)
        {
            result[i] = selector(array[i], i);
        }

        return ImmutableCollectionsMarshal.AsImmutableArray(result);
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TArg">The type of the argument to pass to <paramref name="selector"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on.</param>
    /// <param name="arg">An argument to pass to <paramref name="selector"/>.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/>.
    /// </returns>
    public static ImmutableArray<TResult> SelectAsArray<T, TArg, TResult>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, TResult> selector)
    {
        var length = array.Length;

        if (length == 0)
        {
            return [];
        }

        var result = new TResult[length];

        for (var i = 0; i < length; i++)
        {
            result[i] = selector(array[i], arg);
        }

        return ImmutableCollectionsMarshal.AsImmutableArray(result);
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form by incorporating the element's index.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TArg">The type of the argument to pass to <paramref name="selector"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on.</param>
    /// <param name="arg">An argument to pass to <paramref name="selector"/>.</param>
    /// <param name="selector">
    ///  A transform function to apply to each element; the third parameter of the function represents the index of the element.
    /// </param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/>.
    /// </returns>
    public static ImmutableArray<TResult> SelectAsArray<T, TArg, TResult>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, int, TResult> selector)
    {
        var length = array.Length;

        if (length == 0)
        {
            return [];
        }

        var result = new TResult[length];

        for (var i = 0; i < length; i++)
        {
            result[i] = selector(array[i], arg, i);
        }

        return ImmutableCollectionsMarshal.AsImmutableArray(result);
    }

    public static ImmutableArray<TResult> SelectManyAsArray<TSource, TResult>(this IReadOnlyCollection<TSource>? source, Func<TSource, ImmutableArray<TResult>> selector)
    {
        if (source is null || source.Count == 0)
        {
            return [];
        }

        using var builder = new PooledArrayBuilder<TResult>(capacity: source.Count);
        foreach (var item in source)
        {
            builder.AddRange(selector(item));
        }

        return builder.ToImmutableAndClear();
    }

    /// <summary>
    ///  Filters an <see cref="ImmutableArray{T}"/> based on a predicate.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <param name="array">An <see cref="ImmutableArray{T}"/> to filter.</param>
    /// <param name="predicate">A function to test each element for a condition.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> that contains elements from the input array
    ///  that satisfy the condition.
    /// </returns>
    /// <remarks>
    ///  This method uses an optimized algorithm that avoids allocating a new array if all elements
    ///  match the predicate (returns the original array) or if no elements match (returns an empty array).
    /// </remarks>
    public static ImmutableArray<T> WhereAsArray<T>(this ImmutableArray<T> array, Func<T, bool> predicate)
    {
        var length = array.Length;

        if (length == 0)
        {
            return [];
        }

        using var builder = new PooledArrayBuilder<T>();
        var none = true;
        var all = true;

        for (var i = 0; i < length; i++)
        {
            var item = array[i];

            if (predicate(item))
            {
                none = false;

                if (all)
                {
                    continue;
                }

                Debug.Assert(i > 0);

                builder.Add(item);
            }
            else
            {
                if (none)
                {
                    all = false;
                    continue;
                }

                Debug.Assert(i > 0);

                // We found at least one item that doesn't match the predicate.
                // If this is the first one, we need to copy all the previous items
                // that matched the predicate.
                if (all)
                {
                    all = false;
                    builder.AddRange(array.AsSpan()[..i]);
                }
            }
        }

        Debug.Assert(!(none && all));

        if (all)
        {
            return array;
        }

        if (none)
        {
            return [];
        }

        return builder.ToImmutableAndClear();
    }

    /// <summary>
    ///  Filters an <see cref="ImmutableArray{T}"/> based on a predicate. Each element's index is used in
    ///  the logic of the predicate function.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <param name="array">An <see cref="ImmutableArray{T}"/> to filter.</param>
    /// <param name="predicate">
    ///  A function to test each element for a condition; the second parameter of the function
    ///  represents the index of the element.
    /// </param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> that contains elements from the input array
    ///  that satisfy the condition.
    /// </returns>
    /// <remarks>
    ///  This method uses an optimized algorithm that avoids allocating a new array if all elements
    ///  match the predicate (returns the original array) or if no elements match (returns an empty array).
    /// </remarks>
    public static ImmutableArray<T> WhereAsArray<T>(this ImmutableArray<T> array, Func<T, int, bool> predicate)
    {
        var length = array.Length;

        if (length == 0)
        {
            return [];
        }

        using var builder = new PooledArrayBuilder<T>();
        var none = true;
        var all = true;

        for (var i = 0; i < length; i++)
        {
            var item = array[i];

            if (predicate(item, i))
            {
                none = false;

                if (all)
                {
                    continue;
                }

                Debug.Assert(i > 0);

                builder.Add(item);
            }
            else
            {
                if (none)
                {
                    all = false;
                    continue;
                }

                Debug.Assert(i > 0);

                // We found at least one item that doesn't match the predicate.
                // If this is the first one, we need to copy all the previous items
                // that matched the predicate.
                if (all)
                {
                    all = false;
                    builder.AddRange(array.AsSpan()[..i]);
                }
            }
        }

        Debug.Assert(!(none && all));

        if (all)
        {
            return array;
        }

        if (none)
        {
            return [];
        }

        return builder.ToImmutableAndClear();
    }

    /// <summary>
    ///  Filters an <see cref="ImmutableArray{T}"/> based on a predicate.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TArg">The type of the argument to pass to <paramref name="predicate"/>.</typeparam>
    /// <param name="array">An <see cref="ImmutableArray{T}"/> to filter.</param>
    /// <param name="arg">An argument to pass to <paramref name="predicate"/>.</param>
    /// <param name="predicate">A function to test each element for a condition.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> that contains elements from the input array
    ///  that satisfy the condition.
    /// </returns>
    /// <remarks>
    ///  This method uses an optimized algorithm that avoids allocating a new array if all elements
    ///  match the predicate (returns the original array) or if no elements match (returns an empty array).
    /// </remarks>
    public static ImmutableArray<T> WhereAsArray<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, bool> predicate)
    {
        var length = array.Length;

        if (length == 0)
        {
            return [];
        }

        using var builder = new PooledArrayBuilder<T>();
        var none = true;
        var all = true;

        for (var i = 0; i < length; i++)
        {
            var item = array[i];

            if (predicate(item, arg))
            {
                none = false;

                if (all)
                {
                    continue;
                }

                Debug.Assert(i > 0);

                builder.Add(item);
            }
            else
            {
                if (none)
                {
                    all = false;
                    continue;
                }

                Debug.Assert(i > 0);

                // We found at least one item that doesn't match the predicate.
                // If this is the first one, we need to copy all the previous items
                // that matched the predicate.
                if (all)
                {
                    all = false;
                    builder.AddRange(array.AsSpan()[..i]);
                }
            }
        }

        Debug.Assert(!(none && all));

        if (all)
        {
            return array;
        }

        if (none)
        {
            return [];
        }

        return builder.ToImmutableAndClear();
    }

    /// <summary>
    ///  Filters an <see cref="ImmutableArray{T}"/> based on a predicate. Each element's index is used in
    ///  the logic of the predicate function.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TArg">The type of the argument to pass to <paramref name="predicate"/>.</typeparam>
    /// <param name="array">An <see cref="ImmutableArray{T}"/> to filter.</param>
    /// <param name="arg">An argument to pass to <paramref name="predicate"/>.</param>
    /// <param name="predicate">
    ///  A function to test each element for a condition; the third parameter of the function
    ///  represents the index of the element.
    /// </param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> that contains elements from the input array
    ///  that satisfy the condition.
    /// </returns>
    /// <remarks>
    ///  This method uses an optimized algorithm that avoids allocating a new array if all elements
    ///  match the predicate (returns the original array) or if no elements match (returns an empty array).
    /// </remarks>
    public static ImmutableArray<T> WhereAsArray<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, int, bool> predicate)
    {
        var length = array.Length;

        if (length == 0)
        {
            return [];
        }

        using var builder = new PooledArrayBuilder<T>();
        var none = true;
        var all = true;

        for (var i = 0; i < length; i++)
        {
            var item = array[i];

            if (predicate(item, arg, i))
            {
                none = false;

                if (all)
                {
                    continue;
                }

                Debug.Assert(i > 0);

                builder.Add(item);
            }
            else
            {
                if (none)
                {
                    all = false;
                    continue;
                }

                Debug.Assert(i > 0);

                // We found at least one item that doesn't match the predicate.
                // If this is the first one, we need to copy all the previous items
                // that matched the predicate.
                if (all)
                {
                    all = false;
                    builder.AddRange(array.AsSpan()[..i]);
                }
            }
        }

        Debug.Assert(!(none && all));

        if (all)
        {
            return array;
        }

        if (none)
        {
            return [];
        }

        return builder.ToImmutableAndClear();
    }

    /// <summary>
    ///  Determines whether any element of an array satisfies a condition.
    /// </summary>
    /// <param name="array">
    ///  An <see cref="ImmutableArray{T}"/> whose elements to apply the predicate to.
    /// </param>
    /// <param name="arg">
    ///  An argument to pass to <paramref name="predicate"/>.
    /// </param>
    /// <param name="predicate">
    ///  A function to test each element for a condition.
    /// </param>
    /// <returns>
    ///  <see langword="true"/> if the array is not empty and at least one of its elements passes
    ///  the test in the specified predicate; otherwise, <see langword="false"/>.
    /// </returns>
    public static bool Any<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, bool> predicate)
    {
        foreach (var item in array)
        {
            if (predicate(item, arg))
            {
                return true;
            }
        }

        return false;
    }

    /// <summary>
    ///  Determines whether all elements of an array satisfy a condition.
    /// </summary>
    /// <param name="array">
    ///  An <see cref="ImmutableArray{T}"/> whose elements to apply the predicate to.
    /// </param>
    /// <param name="arg">
    ///  An argument to pass to <paramref name="predicate"/>.
    /// </param>
    /// <param name="predicate">
    ///  A function to test each element for a condition.
    /// </param>
    /// <returns>
    ///  <see langword="true"/> if every element of the array passes the test
    ///  in the specified predicate, or if the array is empty; otherwise,
    ///  <see langword="false"/>.</returns>
    public static bool All<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, bool> predicate)
    {
        foreach (var item in array)
        {
            if (!predicate(item, arg))
            {
                return false;
            }
        }

        return true;
    }

    /// <summary>
    ///  Returns the first element in an array that satisfies a specified condition.
    /// </summary>
    /// <param name="array">
    ///  An <see cref="ImmutableArray{T}"/> to return an element from.
    /// </param>
    /// <param name="arg">
    ///  An argument to pass to <paramref name="predicate"/>.
    /// </param>
    /// <param name="predicate">
    ///  A function to test each element for a condition.
    /// </param>
    /// <returns>
    ///  The first element in the array that passes the test in the specified predicate function.
    /// </returns>
    /// <exception cref="InvalidOperationException">
    ///  No element satisfies the condition in <paramref name="predicate"/>.
    /// </exception>
    public static T First<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, bool> predicate)
    {
        foreach (var item in array)
        {
            if (predicate(item, arg))
            {
                return item;
            }
        }

        return ThrowHelper.ThrowInvalidOperationException<T>(SR.Contains_no_matching_elements);
    }

    /// <summary>
    ///  Returns the first element of the array that satisfies a condition or a default value if no such element is found.
    /// </summary>
    /// <param name="array">
    ///  An <see cref="ImmutableArray{T}"/> to return an element from.
    /// </param>
    /// <param name="arg">
    ///  An argument to pass to <paramref name="predicate"/>.
    /// </param>
    /// <param name="predicate">
    ///  A function to test each element for a condition.
    /// </param>
    /// <returns>
    ///  <see langword="default"/>(<typeparamref name="T"/>) if <paramref name="array"/> is empty or if no element
    ///  passes the test specified by <paramref name="predicate"/>; otherwise, the first element in <paramref name="array"/>
    ///  that passes the test specified by <paramref name="predicate"/>.
    /// </returns>
    public static T? FirstOrDefault<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, bool> predicate)
    {
        foreach (var item in array)
        {
            if (predicate(item, arg))
            {
                return item;
            }
        }

        return default;
    }

    /// <summary>
    ///  Returns the last element of an array that satisfies a specified condition.
    /// </summary>
    /// <param name="array">
    ///  An <see cref="ImmutableArray{T}"/> to return an element from.
    /// </param>
    /// <param name="arg">
    ///  An argument to pass to <paramref name="predicate"/>.
    /// </param>
    /// <param name="predicate">
    ///  A function to test each element for a condition.
    /// </param>
    /// <returns>
    ///  The last element in the array that passes the test in the specified predicate function.
    /// </returns>
    /// <exception cref="InvalidOperationException">
    ///  No element satisfies the condition in <paramref name="predicate"/>.
    /// </exception>
    public static T Last<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, bool> predicate)
    {
        foreach (var item in array.AsSpan().Reversed)
        {
            if (predicate(item, arg))
            {
                return item;
            }
        }

        return ThrowHelper.ThrowInvalidOperationException<T>(SR.Contains_no_matching_elements);
    }

    /// <summary>
    ///  Returns the last element of an array that satisfies a condition or a default value if no such element is found.
    /// </summary>
    /// <param name="array">
    ///  An <see cref="ImmutableArray{T}"/> to return an element from.
    /// </param>
    /// <param name="arg">
    ///  An argument to pass to <paramref name="predicate"/>.
    /// </param>
    /// <param name="predicate">
    ///  A function to test each element for a condition.
    /// </param>
    /// <returns>
    ///  <see langword="default"/>(<typeparamref name="T"/>) if <paramref name="array"/> is empty or if no element
    ///  passes the test specified by <paramref name="predicate"/>; otherwise, the last element in <paramref name="array"/>
    ///  that passes the test specified by <paramref name="predicate"/>.
    /// </returns>
    public static T? LastOrDefault<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, bool> predicate)
    {
        foreach (var item in array.AsSpan().Reversed)
        {
            if (predicate(item, arg))
            {
                return item;
            }
        }

        return default;
    }

    /// <summary>
    ///  Returns the last element of an array that satisfies a condition, or a specified default value if no such element is found.
    /// </summary>
    /// <param name="array">
    ///  An <see cref="ImmutableArray{T}"/> to return an element from.
    /// </param>
    /// <param name="arg">
    ///  An argument to pass to <paramref name="predicate"/>.
    /// </param>
    /// <param name="predicate">
    ///  A function to test each element for a condition.
    /// </param>
    /// <param name="defaultValue">
    ///  The default value to return if the array is empty or no element was found.
    /// </param>
    /// <returns>
    ///  <paramref name="defaultValue"/> if <paramref name="array"/> is empty or if no element
    ///  passes the test specified by <paramref name="predicate"/>; otherwise, the last element in <paramref name="array"/>
    ///  that passes the test specified by <paramref name="predicate"/>.
    /// </returns>
    public static T LastOrDefault<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, bool> predicate, T defaultValue)
    {
        foreach (var item in array.AsSpan().Reversed)
        {
            if (predicate(item, arg))
            {
                return item;
            }
        }

        return defaultValue;
    }

    /// <summary>
    ///  Returns the only element of an array that satisfies a specified condition or a default value
    ///  if no such element exists; this method throws an exception if more than one element satisfies the condition.
    /// </summary>
    /// <param name="array">
    ///  An <see cref="ImmutableArray{T}"/> to return a single element from.
    /// </param>
    /// <param name="arg">
    ///  An argument to pass to <paramref name="predicate"/>.
    /// </param>
    /// <param name="predicate">
    ///  A function to test an element for a condition.
    /// </param>
    /// <returns>
    ///  The single element of the array that satisfies the condition, or
    ///  <see langword="default"/>(<typeparamref name="T"/>) if no such element is found.
    /// </returns>
    /// <exception cref="InvalidOperationException">
    ///  More than one element satisfies the condition in <paramref name="predicate"/>.
    /// </exception>
    public static T? SingleOrDefault<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, bool> predicate)
    {
        var firstSeen = false;
        T? result = default;

        foreach (var item in array)
        {
            if (predicate(item, arg))
            {
                if (firstSeen)
                {
                    return ThrowHelper.ThrowInvalidOperationException<T>(SR.Contains_more_than_one_matching_element);
                }

                firstSeen = true;
                result = item;
            }
        }

        return result;
    }

    /// <summary>
    ///  Returns the only element of an array that satisfies a specified condition, or a specified default value
    ///  if no such element exists; this method throws an exception if more than one element satisfies the condition.
    /// </summary>
    /// <param name="array">
    ///  An <see cref="ImmutableArray{T}"/> to return a single element from.
    /// </param>
    /// <param name="arg">
    ///  An argument to pass to <paramref name="predicate"/>.
    /// </param>
    /// <param name="predicate">
    ///  A function to test an element for a condition.
    /// </param>
    /// <param name="defaultValue">
    ///  The default value to return if the array is empty or no element was found.
    /// </param>
    /// <returns>
    ///  The single element of the array that satisfies the condition, or
    ///  <paramref name="defaultValue"/> if no such element is found.
    /// </returns>
    /// <exception cref="InvalidOperationException">
    ///  More than one element satisfies the condition in <paramref name="predicate"/>.
    /// </exception>
    public static T SingleOrDefault<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, bool> predicate, T defaultValue)
    {
        var firstSeen = false;
        var result = defaultValue;

        foreach (var item in array)
        {
            if (predicate(item, arg))
            {
                if (firstSeen)
                {
                    return ThrowHelper.ThrowInvalidOperationException<T>(SR.Contains_more_than_one_matching_element);
                }

                firstSeen = true;
                result = item;
            }
        }

        return result;
    }

    /// <summary>
    ///  Returns an <see cref="ImmutableArray{T}"/> that contains no duplicates from the <paramref name="source"/> array
    ///  and returns the most recent copy of each item.
    /// </summary>
    /// <param name="source">The array to process.</param>
    public static ImmutableArray<T> GetMostRecentUniqueItems<T>(this ImmutableArray<T> source)
    {
        if (source.IsEmpty)
        {
            return [];
        }

        using var _ = HashSetPool<T>.GetPooledObject(out var uniqueItems);

        return source.GetMostRecentUniqueItems(uniqueItems);
    }

    /// <summary>
    ///  Returns an <see cref="ImmutableArray{T}"/> that contains no duplicates from the <paramref name="source"/> array
    ///  and returns the most recent copy of each item.
    /// </summary>
    /// <param name="source">The array to process.</param>
    /// <param name="comparer">
    ///  A comparer to use for uniqueness.
    /// </param>
    public static ImmutableArray<T> GetMostRecentUniqueItems<T>(this ImmutableArray<T> source, IEqualityComparer<T> comparer)
    {
        if (source.IsEmpty)
        {
            return [];
        }

#if !NETSTANDARD2_0
        var uniqueItems = new HashSet<T>(capacity: source.Length, comparer);
#else
        var uniqueItems = new HashSet<T>(comparer);
#endif

        return source.GetMostRecentUniqueItems(uniqueItems);
    }

    /// <summary>
    ///  Returns an <see cref="ImmutableArray{T}"/> that contains no duplicates from the <paramref name="source"/> array
    ///  and returns the most recent copy of each item.
    /// </summary>
    /// <param name="source">The array to process.</param>
    /// <param name="uniqueItems">
    ///  An empty <see cref="HashSet{T}"/> to use for uniqueness.
    ///  Note that this may still contain items after processing.
    /// </param>
    public static ImmutableArray<T> GetMostRecentUniqueItems<T>(this ImmutableArray<T> source, HashSet<T> uniqueItems)
    {
        if (source.IsEmpty)
        {
            return [];
        }

        return GetMostRecentUniqueItemsCore(source, uniqueItems);
    }

    private static ImmutableArray<T> GetMostRecentUniqueItemsCore<T>(ImmutableArray<T> source, HashSet<T> uniqueItems)
    {
        Debug.Assert(uniqueItems.Count == 0, $"{nameof(uniqueItems)} should be empty!");

        using var stack = new PooledArrayBuilder<T>(capacity: source.Length);

        // Walk the next batch in reverse to identify unique items.
        // We push them on a stack so that we can pop them in order later
        for (var i = source.Length - 1; i >= 0; i--)
        {
            var item = source[i];

            if (uniqueItems.Add(item))
            {
                stack.Push(item);
            }
        }

        // Did we actually dedupe anything? If not, just return the original.
        if (stack.Count == source.Length)
        {
            return source;
        }

        using var result = new PooledArrayBuilder<T>(capacity: stack.Count);

        while (stack.Count > 0)
        {
            result.Add(stack.Pop());
        }

        return result.ToImmutableAndClear();
    }

    /// <summary>
    /// Executes a binary search over an array, but allows the caller to decide what constitutes a match
    /// </summary>
    /// <typeparam name="T">Type of the elements in the array</typeparam>
    /// <typeparam name="TArg">Type of the argument to pass to the comparer</typeparam>
    /// <param name="array">The array to search</param>
    /// <param name="arg">An argument to pass to the comparison function</param>
    /// <param name="comparer">A comparison function that evaluates an item in the array. Return 0 if the item is a match,
    /// or -1 if the item indicates a successful match will be found in the left branch, or 1 if the item indicates a successful
    /// match will be found in the right branch.</param>
    /// <returns>The index of the element found</returns>
    public static int BinarySearchBy<T, TArg>(this ImmutableArray<T> array, TArg arg, Func<T, TArg, int> comparer)
    {
        var min = 0;
        var max = array.Length - 1;

        while (min <= max)
        {
            var mid = (min + max) / 2;
            var comparison = comparer(array[mid], arg);
            if (comparison == 0)
            {
                return mid;
            }

            if (comparison < 0)
            {
                min = mid + 1;
            }
            else
            {
                max = mid - 1;
            }
        }

        return ~min;
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in ascending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in ascending order.
    /// </returns>
    public static ImmutableArray<T> OrderAsArray<T>(this ImmutableArray<T> array)
    {
        var sortHelper = new SortHelper<T>(comparer: null, descending: false);
        return array.OrderAsArrayCore(in sortHelper);
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in ascending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare elements.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in ascending order.
    /// </returns>
    public static ImmutableArray<T> OrderAsArray<T>(this ImmutableArray<T> array, IComparer<T> comparer)
    {
        var sortHelper = new SortHelper<T>(comparer, descending: false);
        return array.OrderAsArrayCore(in sortHelper);
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in ascending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <param name="comparison">A <see cref="Comparison{T}"/> to compare elements.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in ascending order.
    /// </returns>
    public static ImmutableArray<T> OrderAsArray<T>(this ImmutableArray<T> array, Comparison<T> comparison)
    {
        var sortHelper = new SortHelper<T>(comparison, descending: false);
        return array.OrderAsArrayCore(in sortHelper);
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in descending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in descending order.
    /// </returns>
    public static ImmutableArray<T> OrderDescendingAsArray<T>(this ImmutableArray<T> array)
    {
        var sortHelper = new SortHelper<T>(comparer: null, descending: true);
        return array.OrderAsArrayCore(in sortHelper);
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in descending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare elements.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in descending order.
    /// </returns>
    public static ImmutableArray<T> OrderDescendingAsArray<T>(this ImmutableArray<T> array, IComparer<T> comparer)
    {
        var sortHelper = new SortHelper<T>(comparer, descending: true);
        return array.OrderAsArrayCore(in sortHelper);
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in descending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <param name="comparison">A <see cref="Comparison{T}"/> to compare elements.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in descending order.
    /// </returns>
    public static ImmutableArray<T> OrderDescendingAsArray<T>(this ImmutableArray<T> array, Comparison<T> comparison)
    {
        var sortHelper = new SortHelper<T>(comparison, descending: true);
        return array.OrderAsArrayCore(in sortHelper);
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in ascending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in ascending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> OrderByAsArray<TElement, TKey>(
        this ImmutableArray<TElement> array, Func<TElement, TKey> keySelector)
    {
        var sortHelper = new SortHelper<TKey>(comparer: null, descending: false);
        return array.OrderByAsArrayCore(keySelector, in sortHelper);
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in ascending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in ascending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> OrderByAsArray<TElement, TKey>(
        this ImmutableArray<TElement> array, Func<TElement, TKey> keySelector, IComparer<TKey> comparer)
    {
        var sortHelper = new SortHelper<TKey>(comparer, descending: false);
        return array.OrderByAsArrayCore(keySelector, in sortHelper);
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in ascending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparison">A <see cref="Comparison{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in ascending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> OrderByAsArray<TElement, TKey>(
        this ImmutableArray<TElement> array, Func<TElement, TKey> keySelector, Comparison<TKey> comparison)
    {
        var sortHelper = new SortHelper<TKey>(comparison, descending: false);
        return array.OrderByAsArrayCore(keySelector, in sortHelper);
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in descending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in descending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> OrderByDescendingAsArray<TElement, TKey>(
        this ImmutableArray<TElement> array, Func<TElement, TKey> keySelector)
    {
        var sortHelper = new SortHelper<TKey>(comparer: null, descending: true);
        return array.OrderByAsArrayCore(keySelector, in sortHelper);
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in descending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in descending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> OrderByDescendingAsArray<TElement, TKey>(
        this ImmutableArray<TElement> array, Func<TElement, TKey> keySelector, IComparer<TKey> comparer)
    {
        var sortHelper = new SortHelper<TKey>(comparer, descending: true);
        return array.OrderByAsArrayCore(keySelector, in sortHelper);
    }

    /// <summary>
    ///  Sorts the elements of an <see cref="ImmutableArray{T}"/> in descending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="array">An array to be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparison">A <see cref="Comparison{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in descending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> OrderByDescendingAsArray<TElement, TKey>(
        this ImmutableArray<TElement> array, Func<TElement, TKey> keySelector, Comparison<TKey> comparison)
    {
        var sortHelper = new SortHelper<TKey>(comparison, descending: true);
        return array.OrderByAsArrayCore(keySelector, in sortHelper);
    }

    private static ImmutableArray<T> OrderAsArrayCore<T>(this ImmutableArray<T> array, ref readonly SortHelper<T> sortHelper)
        => array.OrderByAsArrayCore(SortHelper<T>.IdentityFunc, in sortHelper);

    private static ImmutableArray<TElement> OrderByAsArrayCore<TElement, TKey>(
        this ImmutableArray<TElement> array, Func<TElement, TKey> keySelector, ref readonly SortHelper<TKey> sortHelper)
    {
        if (array.Length <= 1)
        {
            return array;
        }

        var items = array.AsSpan();
        var length = items.Length;

        using var keys = SortKey<TKey>.GetPooledArray(minimumLength: length);

        if (sortHelper.ComputeKeys(items, keySelector, keys.Span))
        {
            // The keys are already ordered, so we don't need to create a new array and sort it.
            return array;
        }

        var newArray = new TElement[length];
        items.CopyTo(newArray);

        Array.Sort(keys.Array, newArray, 0, length, sortHelper.GetOrCreateComparer());

        return ImmutableCollectionsMarshal.AsImmutableArray(newArray);
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> and clears the collection.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be cleared.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/>.
    /// </returns>
    /// <remarks>
    /// This method is preferred over calling DrainToImmutable as it allows reuse of the
    /// backing array in the common case where the builder isn't fully utilizing it's capacity.
    /// </remarks>
    public static ImmutableArray<T> ToImmutableAndClear<T>(this ImmutableArray<T>.Builder builder)
    {
        ImmutableArray<T> result;
        if (builder.Count != builder.Capacity)
        {
            result = builder.ToImmutable();
            builder.Clear();
        }
        else
        {
            result = builder.DrainToImmutable();
        }

        return result;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in ascending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in ascending order.
    /// </returns>
    public static ImmutableArray<T> ToImmutableOrdered<T>(this ImmutableArray<T>.Builder builder)
    {
        var array = builder.ToImmutable();
        array.Unsafe().Order();
        return array;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in ascending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare elements.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in ascending order.
    /// </returns>
    public static ImmutableArray<T> ToImmutableOrdered<T>(this ImmutableArray<T>.Builder builder, IComparer<T> comparer)
    {
        var array = builder.ToImmutable();
        array.Unsafe().Order(comparer);
        return array;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in ascending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="comparison">An <see cref="Comparison{T}"/> to compare elements.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in ascending order.
    /// </returns>
    public static ImmutableArray<T> ToImmutableOrdered<T>(this ImmutableArray<T>.Builder builder, Comparison<T> comparison)
    {
        var array = builder.ToImmutable();
        array.Unsafe().Order(comparison);
        return array;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in descending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in descending order.
    /// </returns>
    public static ImmutableArray<T> ToImmutableOrderedDescending<T>(this ImmutableArray<T>.Builder builder)
    {
        var array = builder.ToImmutable();
        array.Unsafe().OrderDescending();
        return array;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in descending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare elements.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in descending order.
    /// </returns>
    public static ImmutableArray<T> ToImmutableOrderedDescending<T>(this ImmutableArray<T>.Builder builder, IComparer<T> comparer)
    {
        var array = builder.ToImmutable();
        array.Unsafe().OrderDescending(comparer);
        return array;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in descending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="comparison">An <see cref="Comparison{T}"/> to compare elements.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in descending order.
    /// </returns>
    public static ImmutableArray<T> ToImmutableOrderedDescending<T>(this ImmutableArray<T>.Builder builder, Comparison<T> comparison)
    {
        var array = builder.ToImmutable();
        array.Unsafe().OrderDescending(comparison);
        return array;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in ascending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in ascending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> ToImmutableOrderedBy<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector)
    {
        var array = builder.ToImmutable();
        array.Unsafe().OrderBy(keySelector);
        return array;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in ascending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in ascending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> ToImmutableOrderedBy<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector, IComparer<TKey> comparer)
    {
        var array = builder.ToImmutable();
        array.Unsafe().OrderBy(keySelector, comparer);
        return array;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in ascending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparison">An <see cref="Comparison{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in ascending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> ToImmutableOrderedBy<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector, Comparison<TKey> comparison)
    {
        var array = builder.ToImmutable();
        array.Unsafe().OrderBy(keySelector, comparison);
        return array;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in descending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in descending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> ToImmutableOrderedByDescending<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector)
    {
        var array = builder.ToImmutable();
        array.Unsafe().OrderByDescending(keySelector);
        return array;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in descending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in descending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> ToImmutableOrderedByDescending<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector, IComparer<TKey> comparer)
    {
        var array = builder.ToImmutable();
        array.Unsafe().OrderByDescending(keySelector, comparer);
        return array;
    }

    /// <summary>
    ///  Returns an immutable array that contains the current contents of this
    ///  <see cref="ImmutableArray{T}.Builder"/> sorted in descending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparison">An <see cref="Comparison{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are sorted in descending order according to a key.
    /// </returns>
    public static ImmutableArray<TElement> ToImmutableOrderedByDescending<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector, Comparison<TKey> comparison)
    {
        var array = builder.ToImmutable();
        array.Unsafe().OrderByDescending(keySelector, comparison);
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in ascending order and sets the collection to a zero length array.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in ascending order.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<T> ToImmutableOrderedAndClear<T>(this ImmutableArray<T>.Builder builder)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().Order();
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in ascending order and sets the collection to a zero length array.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare elements.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in ascending order.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<T> ToImmutableOrderedAndClear<T>(this ImmutableArray<T>.Builder builder, IComparer<T> comparer)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().Order(comparer);
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in ascending order and sets the collection to a zero length array.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="comparison">An <see cref="Comparison{T}"/> to compare elements.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in ascending order.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<T> ToImmutableOrderedAndClear<T>(this ImmutableArray<T>.Builder builder, Comparison<T> comparison)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().Order(comparison);
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in descending order and sets the collection to a zero length array.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in descending order.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<T> ToImmutableOrderedDescendingAndClear<T>(this ImmutableArray<T>.Builder builder)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().OrderDescending();
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in descending order and sets the collection to a zero length array.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare elements.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in descending order.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<T> ToImmutableOrderedDescendingAndClear<T>(this ImmutableArray<T>.Builder builder, IComparer<T> comparer)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().OrderDescending(comparer);
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in descending order and sets the collection to a zero length array.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="comparison">An <see cref="Comparison{T}"/> to compare elements.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in descending order.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<T> ToImmutableOrderedDescendingAndClear<T>(this ImmutableArray<T>.Builder builder, Comparison<T> comparison)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().OrderDescending(comparison);
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in ascending order according to a key and sets
    ///  the collection to a zero length array.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in ascending order according to a key.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<TElement> ToImmutableOrderedByAndClear<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().OrderBy(keySelector);
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in ascending order according to a key and sets
    ///  the collection to a zero length array.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in ascending order according to a key.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<TElement> ToImmutableOrderedByAndClear<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector, IComparer<TKey> comparer)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().OrderBy(keySelector, comparer);
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in ascending order according to a key and sets
    ///  the collection to a zero length array.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparison">An <see cref="Comparison{T}"/> to compare keys.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in ascending order according to a key.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<TElement> ToImmutableOrderedByAndClear<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector, Comparison<TKey> comparison)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().OrderBy(keySelector, comparison);
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in descending order according to a key and sets
    ///  the collection to a zero length array.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in descending order according to a key.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<TElement> ToImmutableOrderedByDescendingAndClear<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().OrderByDescending(keySelector);
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in descending order according to a key and sets
    ///  the collection to a zero length array.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in descending order according to a key.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<TElement> ToImmutableOrderedByDescendingAndClear<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector, IComparer<TKey> comparer)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().OrderByDescending(keySelector, comparer);
        return array;
    }

    /// <summary>
    ///  Returns the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  as an immutable array sorted in descending order according to a key and sets
    ///  the collection to a zero length array.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="builder"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <param name="builder">The <see cref="ImmutableArray{T}.Builder"/> whose contents will be sorted.</param>
    /// <param name="keySelector">A function to extract a key from an element.</param>
    /// <param name="comparison">An <see cref="Comparison{T}"/> to compare keys.</param>
    /// <returns>
    ///  An immutable array that contains the current contents of this <see cref="ImmutableArray{T}.Builder"/>
    ///  sorted in descending order according to a key.
    /// </returns>
    /// <remarks>
    ///  If <see cref="ImmutableArray{T}.Builder.Capacity">Capacity</see> equals
    ///  <see cref="ImmutableArray{T}.Builder.Count">Count</see>, the internal array will be extracted as an
    ///  <see cref="ImmutableArray{T}"/> without copying the contents. Otherwise, the contents will be copied
    ///  into a new array. The collection will then be set to a zero length array.
    /// </remarks>
    public static ImmutableArray<TElement> ToImmutableOrderedByDescendingAndClear<TElement, TKey>(
        this ImmutableArray<TElement>.Builder builder, Func<TElement, TKey> keySelector, Comparison<TKey> comparison)
    {
        var array = builder.ToImmutableAndClear();
        array.Unsafe().OrderByDescending(keySelector, comparison);
        return array;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in ascending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in ascending order.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderAsArray<T, TResult>(this ImmutableArray<T> array, Func<T, TResult> selector)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().Order();

        return result;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in ascending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare projected elements.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in ascending order.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderAsArray<T, TResult>(
        this ImmutableArray<T> array, Func<T, TResult> selector, IComparer<TResult> comparer)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().Order(comparer);

        return result;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in ascending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <param name="comparison">A <see cref="Comparison{T}"/> to compare elements.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in ascending order.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderAsArray<T, TResult>(
        this ImmutableArray<T> array, Func<T, TResult> selector, Comparison<TResult> comparison)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().Order(comparison);

        return result;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in descending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in descending order.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderDescendingAsArray<T, TResult>(this ImmutableArray<T> array, Func<T, TResult> selector)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().OrderDescending();

        return result;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in descending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare elements.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in descending order.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderDescendingAsArray<T, TResult>(
        this ImmutableArray<T> array, Func<T, TResult> selector, IComparer<TResult> comparer)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().OrderDescending(comparer);

        return result;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in descending order.
    /// </summary>
    /// <typeparam name="T">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <param name="comparison">A <see cref="Comparison{T}"/> to compare elements.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in descending order.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderDescendingAsArray<T, TResult>(
        this ImmutableArray<T> array, Func<T, TResult> selector, Comparison<TResult> comparison)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().OrderDescending(comparison);

        return result;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in ascending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <param name="keySelector">A function to extract a key from a projected element.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in ascending order according to a key.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderByAsArray<TElement, TKey, TResult>(
        this ImmutableArray<TElement> array, Func<TElement, TResult> selector, Func<TResult, TKey> keySelector)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().OrderBy(keySelector);

        return result;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in ascending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <param name="keySelector">A function to extract a key from a projected element.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in ascending order according to a key.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderByAsArray<TElement, TKey, TResult>(
        this ImmutableArray<TElement> array, Func<TElement, TResult> selector, Func<TResult, TKey> keySelector, IComparer<TKey> comparer)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().OrderBy(keySelector, comparer);

        return result;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in ascending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <param name="keySelector">A function to extract a key from a projected element.</param>
    /// <param name="comparison">A <see cref="Comparison{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in ascending order according to a key.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderByAsArray<TElement, TKey, TResult>(
        this ImmutableArray<TElement> array, Func<TElement, TResult> selector, Func<TResult, TKey> keySelector, Comparison<TKey> comparison)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().OrderBy(keySelector, comparison);

        return result;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in descending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <param name="keySelector">A function to extract a key from a projected element.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in descending order according to a key.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderByDescendingAsArray<TElement, TKey, TResult>(
        this ImmutableArray<TElement> array, Func<TElement, TResult> selector, Func<TResult, TKey> keySelector)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().OrderByDescending(keySelector);

        return result;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in descending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <param name="keySelector">A function to extract a key from a projected element.</param>
    /// <param name="comparer">An <see cref="IComparer{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in descending order according to a key.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderByDescendingAsArray<TElement, TKey, TResult>(
        this ImmutableArray<TElement> array, Func<TElement, TResult> selector, Func<TResult, TKey> keySelector, IComparer<TKey> comparer)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().OrderByDescending(keySelector, comparer);

        return result;
    }

    /// <summary>
    ///  Projects each element of an <see cref="ImmutableArray{T}"/> into a new form and sorts them in descending order according to a key.
    /// </summary>
    /// <typeparam name="TElement">The type of the elements in <paramref name="array"/>.</typeparam>
    /// <typeparam name="TKey">The type of key returned by <paramref name="keySelector"/>.</typeparam>
    /// <typeparam name="TResult">The type of the value returned by <paramref name="selector"/>.</typeparam>
    /// <param name="array">An array of values to invoke a transform function on and sort.</param>
    /// <param name="selector">A transform function to apply to each element.</param>
    /// <param name="keySelector">A function to extract a key from a projected element.</param>
    /// <param name="comparison">A <see cref="Comparison{T}"/> to compare keys.</param>
    /// <returns>
    ///  Returns a new <see cref="ImmutableArray{T}"/> whose elements are the result of invoking the transform function
    ///  on each element of <paramref name="array"/> and sorted in descending order according to a key.
    /// </returns>
    public static ImmutableArray<TResult> SelectAndOrderByDescendingAsArray<TElement, TKey, TResult>(
        this ImmutableArray<TElement> array, Func<TElement, TResult> selector, Func<TResult, TKey> keySelector, Comparison<TKey> comparison)
    {
        var result = array.SelectAsArray(selector);
        result.Unsafe().OrderByDescending(keySelector, comparison);

        return result;
    }
}
