#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace System.Linq
{
    public static partial class AsyncEnumerable
    {
<#
foreach (var m in new[] { "Max", "Min" })
{
    var comparison = m == "Max" ? ">" : "<";
    var extremum = m == "Max" ? "maximum" : "minimum";
    foreach (var t in new[] { "int", "int?", "long", "long?", "float", "float?", "double", "double?", "decimal", "decimal?" })
    {
        var isFloatingPoint = t.StartsWith("float") || t.StartsWith("double");
        var isInteger = t.StartsWith("int") || t.StartsWith("long");
        var isNullable = t.EndsWith("?");
        var shortCircuit = t.StartsWith("decimal");
        var typeStr = t;
        if (isNullable) {
            typeStr = "Nullable{" + t.Substring(0, 1).ToUpper() + t.Substring(1, t.Length - 2) + "}";
        }
#>
        /// 
        /// Returns the <#=extremum#> value in an async-enumerable sequence of  values.
        /// 
        /// A sequence of  values to determine the <#=extremum#> value of.
        /// The optional cancellation token to be used for cancelling the sequence at any time.
        /// A ValueTask containing a single element with the <#=extremum#> value in the source sequence.
        ///  is null.
        public static ValueTask<<#=t#>> <#=m#>Async(this IAsyncEnumerable<<#=t#>> source, CancellationToken cancellationToken = default)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            return Core(source, cancellationToken);
            static async ValueTask<<#=t#>> Core(IAsyncEnumerable<<#=t#>> source, CancellationToken cancellationToken)
            {
<#
        if (!isNullable)
        {
#>
                <#=t#> value;
                await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
                {
                    if (!await e.MoveNextAsync())
                    {
                        throw Error.NoElements();
                    }
                    value = e.Current;
<#
            if (isFloatingPoint && m == "Max")
            {
#>
                    // NaN is ordered less than all other values. We need to do explicit checks
                    // to ensure this, but once we've found a value that is not NaN we need no
                    // longer worry about it, so first loop until such a value is found (or not,
                    // as the case may be).
                    while (<#=t#>.IsNaN(value))
                    {
                        if (!await e.MoveNextAsync())
                        {
                            return value;
                        }
                        value = e.Current;
                    }
<#
            }
#>
                    while (await e.MoveNextAsync())
                    {
                        var x = e.Current;
                        if (x <#=comparison#> value)
                        {
                            value = x;
                        }
<#
            if (isFloatingPoint && m == "Min")
            {
#>
                        else
                        {
                            // Normally NaN < anything is false, as is anything < NaN
                            // However, this leads to some irksome outcomes in Min and Max.
                            // If we use those semantics then Min(NaN, 5.0) is NaN, but
                            // Min(5.0, NaN) is 5.0!  To fix this, we impose a total
                            // ordering where NaN is smaller than every value, including
                            // negative infinity.
                            // Not testing for NaN therefore isn't an option, but since we
                            // can't find a smaller value, we can short-circuit.
                            if (<#=t#>.IsNaN(x))
                            {
                                return x;
                            }
                        }
<#
            }
#>
                    }
                }
                return value;
<#
        }
        else
        {
#>
                <#=t#> value = null;
                await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
                {
                    // Start off knowing that we've a non-null value (or exit here, knowing we don't)
                    // so we don't have to keep testing for nullity.
                    do
                    {
                        if (!await e.MoveNextAsync())
                        {
                            return value;
                        }
                        value = e.Current;
                    }
                    while (!value.HasValue);
                    // Keep hold of the wrapped value, and do comparisons on that, rather than
                    // using the lifted operation each time.
                    var valueVal = value.GetValueOrDefault();
<#
            if (isInteger && m == "Max")
            {
#>
                    if (valueVal >= 0)
                    {
                        // We can fast-path this case where we know HasValue will
                        // never affect the outcome, without constantly checking
                        // if we're in such a state. Similar fast-paths could
                        // be done for other cases, but as all-positive or mostly-
                        // positive integer values are quite common in real-world
                        // uses, it's only been done for int? and long?.
                        while (await e.MoveNextAsync())
                        {
                            var cur = e.Current;
                            var x = cur.GetValueOrDefault();
                            if (x <#=comparison#> valueVal)
                            {
                                valueVal = x;
                                value = cur;
                            }
                        }
                    }
                    else
                    {
                        while (await e.MoveNextAsync())
                        {
                            var cur = e.Current;
                            var x = cur.GetValueOrDefault();
                            // Do not replace & with &&. The branch prediction cost outweighs the extra operation
                            // unless nulls either never happen or always happen.
                            if (cur.HasValue & x <#=comparison#> valueVal)
                            {
                                valueVal = x;
                                value = cur;
                            }
                        }
                    }
<#
            }
            else if (isFloatingPoint && m == "Min")
            {
#>
                    while (await e.MoveNextAsync())
                    {
                        var cur = e.Current;
                        if (cur.HasValue)
                        {
                            var x = cur.GetValueOrDefault();
                            if (x <#=comparison#> valueVal)
                            {
                                valueVal = x;
                                value = cur;
                            }
                            else
                            {
                                // Normally NaN < anything is false, as is anything < NaN
                                // However, this leads to some irksome outcomes in Min and Max.
                                // If we use those semantics then Min(NaN, 5.0) is NaN, but
                                // Min(5.0, NaN) is 5.0!  To fix this, we impose a total
                                // ordering where NaN is smaller than every value, including
                                // negative infinity.
                                // Not testing for NaN therefore isn't an option, but since we
                                // can't find a smaller value, we can short-circuit.
                                if (<#=t.TrimEnd('?')#>.IsNaN(x))
                                {
                                    return cur;
                                }
                            }
                        }
                    }
<#
            }
            else
            {
                if (isFloatingPoint && m == "Max")
                {
#>
                    // NaN is ordered less than all other values. We need to do explicit checks
                    // to ensure this, but once we've found a value that is not NaN we need no
                    // longer worry about it, so first loop until such a value is found (or not,
                    // as the case may be).
                    while (<#=t.TrimEnd('?')#>.IsNaN(valueVal))
                    {
                        if (!await e.MoveNextAsync())
                        {
                            return value;
                        }
                        var cur = e.Current;
                        if (cur.HasValue)
                        {
                            valueVal = (value = cur).GetValueOrDefault();
                        }
                    }
<#
                }
#>
                    while (await e.MoveNextAsync())
                    {
                        var cur = e.Current;
                        var x = cur.GetValueOrDefault();
<#
                if (shortCircuit)
                {
#>
                        if (cur.HasValue && x <#=comparison#> valueVal)
<#
                }
                else
                {
#>
                        // Do not replace & with &&. The branch prediction cost outweighs the extra operation
                        // unless nulls either never happen or always happen.
                        if (cur.HasValue & x <#=comparison#> valueVal)
<#
                }
#>
                        {
                            valueVal = x;
                            value = cur;
                        }
                    }
<#
            }
#>
                }
                return value;
<#
        }
#>
            }
        }
<#
foreach (var overload in new[] {
    new { selector = "Func", invoke = "selector(e.Current)" },
    new { selector = "Func>", invoke = "await selector(e.Current).ConfigureAwait(false)" },
    new { selector = "Func>", invoke = "await selector(e.Current, cancellationToken).ConfigureAwait(false)" },
})
{
    var isAsync = overload.invoke.StartsWith("await");
    var isDeepCancellation = overload.selector.Contains("CancellationToken");
    var suffix = isAsync ? "Await" : "";
    var visibility = isAsync ? "internal" : "public";
    var core = isAsync ? "Core" : "";
    if (isDeepCancellation)
    {
        suffix += "WithCancellation";
#>
#if !NO_DEEP_CANCELLATION
<#
    }
#>
        <#=visibility#> static ValueTask<<#=t#>> <#=m#><#=suffix#>Async<#=core#>(this IAsyncEnumerable source, <#=overload.selector#> selector, CancellationToken cancellationToken = default)
        {
            if (source == null)
                throw Error.ArgumentNull(nameof(source));
            if (selector == null)
                throw Error.ArgumentNull(nameof(selector));
            return Core(source, selector, cancellationToken);
            static async ValueTask<<#=t#>> Core(IAsyncEnumerable source, <#=overload.selector#> selector, CancellationToken cancellationToken)
            {
<#
        if (!isNullable)
        {
#>
                <#=t#> value;
                await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
                {
                    if (!await e.MoveNextAsync())
                    {
                        throw Error.NoElements();
                    }
                    value = <#=overload.invoke#>;
<#
            if (isFloatingPoint && m == "Max")
            {
#>
                    // NaN is ordered less than all other values. We need to do explicit checks
                    // to ensure this, but once we've found a value that is not NaN we need no
                    // longer worry about it, so first loop until such a value is found (or not,
                    // as the case may be).
                    while (<#=t#>.IsNaN(value))
                    {
                        if (!await e.MoveNextAsync())
                        {
                            return value;
                        }
                        value = <#=overload.invoke#>;
                    }
<#
            }
#>
                    while (await e.MoveNextAsync())
                    {
                        var x = <#=overload.invoke#>;
                        if (x <#=comparison#> value)
                        {
                            value = x;
                        }
<#
            if (isFloatingPoint && m == "Min")
            {
#>
                        else
                        {
                            // Normally NaN < anything is false, as is anything < NaN
                            // However, this leads to some irksome outcomes in Min and Max.
                            // If we use those semantics then Min(NaN, 5.0) is NaN, but
                            // Min(5.0, NaN) is 5.0!  To fix this, we impose a total
                            // ordering where NaN is smaller than every value, including
                            // negative infinity.
                            // Not testing for NaN therefore isn't an option, but since we
                            // can't find a smaller value, we can short-circuit.
                            if (<#=t#>.IsNaN(x))
                            {
                                return x;
                            }
                        }
<#
            }
#>
                    }
                }
                return value;
<#
        }
        else
        {
#>
                <#=t#> value = null;
                await using (var e = source.GetConfiguredAsyncEnumerator(cancellationToken, false))
                {
                    // Start off knowing that we've a non-null value (or exit here, knowing we don't)
                    // so we don't have to keep testing for nullity.
                    do
                    {
                        if (!await e.MoveNextAsync())
                        {
                            return value;
                        }
                        value = <#=overload.invoke#>;
                    }
                    while (!value.HasValue);
                    // Keep hold of the wrapped value, and do comparisons on that, rather than
                    // using the lifted operation each time.
                    var valueVal = value.GetValueOrDefault();
<#
            if (isInteger && m == "Max")
            {
#>
                    if (valueVal >= 0)
                    {
                        // We can fast-path this case where we know HasValue will
                        // never affect the outcome, without constantly checking
                        // if we're in such a state. Similar fast-paths could
                        // be done for other cases, but as all-positive or mostly-
                        // positive integer values are quite common in real-world
                        // uses, it's only been done for int? and long?.
                        while (await e.MoveNextAsync())
                        {
                            var cur = <#=overload.invoke#>;
                            var x = cur.GetValueOrDefault();
                            if (x <#=comparison#> valueVal)
                            {
                                valueVal = x;
                                value = cur;
                            }
                        }
                    }
                    else
                    {
                        while (await e.MoveNextAsync())
                        {
                            var cur = <#=overload.invoke#>;
                            var x = cur.GetValueOrDefault();
                            // Do not replace & with &&. The branch prediction cost outweighs the extra operation
                            // unless nulls either never happen or always happen.
                            if (cur.HasValue & x <#=comparison#> valueVal)
                            {
                                valueVal = x;
                                value = cur;
                            }
                        }
                    }
<#
            }
            else if (isFloatingPoint && m == "Min")
            {
#>
                    while (await e.MoveNextAsync())
                    {
                        var cur = <#=overload.invoke#>;
                        if (cur.HasValue)
                        {
                            var x = cur.GetValueOrDefault();
                            if (x <#=comparison#> valueVal)
                            {
                                valueVal = x;
                                value = cur;
                            }
                            else
                            {
                                // Normally NaN < anything is false, as is anything < NaN
                                // However, this leads to some irksome outcomes in Min and Max.
                                // If we use those semantics then Min(NaN, 5.0) is NaN, but
                                // Min(5.0, NaN) is 5.0!  To fix this, we impose a total
                                // ordering where NaN is smaller than every value, including
                                // negative infinity.
                                // Not testing for NaN therefore isn't an option, but since we
                                // can't find a smaller value, we can short-circuit.
                                if (<#=t.TrimEnd('?')#>.IsNaN(x))
                                {
                                    return cur;
                                }
                            }
                        }
                    }
<#
            }
            else
            {
                if (isFloatingPoint && m == "Max")
                {
#>
                    // NaN is ordered less than all other values. We need to do explicit checks
                    // to ensure this, but once we've found a value that is not NaN we need no
                    // longer worry about it, so first loop until such a value is found (or not,
                    // as the case may be).
                    while (<#=t.TrimEnd('?')#>.IsNaN(valueVal))
                    {
                        if (!await e.MoveNextAsync())
                        {
                            return value;
                        }
                        var cur = <#=overload.invoke#>;
                        if (cur.HasValue)
                        {
                            valueVal = (value = cur).GetValueOrDefault();
                        }
                    }
<#
                }
#>
                    while (await e.MoveNextAsync())
                    {
                        var cur = <#=overload.invoke#>;
                        var x = cur.GetValueOrDefault();
<#
                if (shortCircuit)
                {
#>
                        if (cur.HasValue && x <#=comparison#> valueVal)
<#
                }
                else
                {
#>
                        // Do not replace & with &&. The branch prediction cost outweighs the extra operation
                        // unless nulls either never happen or always happen.
                        if (cur.HasValue & x <#=comparison#> valueVal)
<#
                }
#>
                        {
                            valueVal = x;
                            value = cur;
                        }
                    }
<#
            }
#>
                }
                return value;
<#
        }
#>
            }
        }
<#
    if (isDeepCancellation)
    {
#>
#endif
<#
    }
}
#>
<#
    }
}
#>
    }
}