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

using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Text.Json;

namespace Microsoft.AspNetCore.Components
{
    /// <summary>
    /// Performs conversions during binding.
    /// </summary>
    //
    // Perf: our conversion routines present a regular API surface that allows us to specialize on types to avoid boxing.
    // for instance, many of these types could be cast to IFormattable to do the appropriate formatting, but that's going
    // to allocate.
    public static class BindConverter
    {
        private static readonly object BoxedTrue = true;
        private static readonly object BoxedFalse = false;

        private delegate object? BindFormatter<T>(T value, CultureInfo? culture);

        internal delegate bool BindParser<T>(object? obj, CultureInfo? culture, [MaybeNullWhen(false)] out T value);
        internal delegate bool BindParserWithFormat<T>(object? obj, CultureInfo? culture, string? format, [MaybeNullWhen(false)] out T value);

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(string? value, CultureInfo? culture = null) => FormatStringValueCore(value, culture);

        private static string? FormatStringValueCore(string? value, CultureInfo? culture)
        {
            return value;
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static bool FormatValue(bool value, CultureInfo? culture = null)
        {
            // Formatting for bool is special-cased. We need to produce a boolean value for conditional attributes
            // to work.
            return value;
        }

        // Used with generics
        private static object FormatBoolValueCore(bool value, CultureInfo? culture)
        {
            // Formatting for bool is special-cased. We need to produce a boolean value for conditional attributes
            // to work.
            return value ? BoxedTrue : BoxedFalse;
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static bool? FormatValue(bool? value, CultureInfo? culture = null)
        {
            // Formatting for bool is special-cased. We need to produce a boolean value for conditional attributes
            // to work.
            return value == null ? (bool?)null : value.Value;
        }

        // Used with generics
        private static object? FormatNullableBoolValueCore(bool? value, CultureInfo? culture)
        {
            // Formatting for bool is special-cased. We need to produce a boolean value for conditional attributes
            // to work.
            return value == null ? null : value.Value ? BoxedTrue : BoxedFalse;
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(int value, CultureInfo? culture = null) => FormatIntValueCore(value, culture);

        private static string? FormatIntValueCore(int value, CultureInfo? culture)
        {
            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(int? value, CultureInfo? culture = null) => FormatNullableIntValueCore(value, culture);

        private static string? FormatNullableIntValueCore(int? value, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(long value, CultureInfo? culture = null) => FormatLongValueCore(value, culture);

        private static string FormatLongValueCore(long value, CultureInfo? culture)
        {
            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(long? value, CultureInfo? culture = null) => FormatNullableLongValueCore(value, culture);

        private static string? FormatNullableLongValueCore(long? value, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(short value, CultureInfo? culture = null) => FormatShortValueCore(value, culture);

        private static string FormatShortValueCore(short value, CultureInfo? culture)
        {
            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(short? value, CultureInfo? culture = null) => FormatNullableShortValueCore(value, culture);

        private static string? FormatNullableShortValueCore(short? value, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(float value, CultureInfo? culture = null) => FormatFloatValueCore(value, culture);

        private static string FormatFloatValueCore(float value, CultureInfo? culture)
        {
            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(float? value, CultureInfo? culture = null) => FormatNullableFloatValueCore(value, culture);

        private static string? FormatNullableFloatValueCore(float? value, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(double value, CultureInfo? culture = null) => FormatDoubleValueCore(value, culture);

        private static string FormatDoubleValueCore(double value, CultureInfo? culture)
        {
            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(double? value, CultureInfo? culture = null) => FormatNullableDoubleValueCore(value, culture);

        private static string? FormatNullableDoubleValueCore(double? value, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> for inclusion in an attribute.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(decimal value, CultureInfo? culture = null) => FormatDecimalValueCore(value, culture);

        private static string FormatDecimalValueCore(decimal value, CultureInfo? culture)
        {
            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(decimal? value, CultureInfo? culture = null) => FormatNullableDecimalValueCore(value, culture);

        private static string? FormatNullableDecimalValueCore(decimal? value, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(DateTime value, CultureInfo? culture = null) => FormatDateTimeValueCore(value, format: null, culture);

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="format">The format to use. Provided to <see cref="DateTimeOffset.ToString(string, IFormatProvider)"/>.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(DateTime value, string format, CultureInfo? culture = null) => FormatDateTimeValueCore(value, format, culture);

        private static string FormatDateTimeValueCore(DateTime value, string? format, CultureInfo? culture)
        {
            if (format != null)
            {
                return value.ToString(format, culture ?? CultureInfo.CurrentCulture);
            }

            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        private static string FormatDateTimeValueCore(DateTime value, CultureInfo? culture)
        {
            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(DateTime? value, CultureInfo? culture = null) => FormatNullableDateTimeValueCore(value, format: null, culture);

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="format">The format to use. Provided to <see cref="DateTime.ToString(string, IFormatProvider)"/>.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(DateTime? value, string? format, CultureInfo? culture = null) => FormatNullableDateTimeValueCore(value, format, culture);

        private static string? FormatNullableDateTimeValueCore(DateTime? value, string? format, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            if (format != null)
            {
                return value.Value.ToString(format, culture ?? CultureInfo.CurrentCulture);
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        private static string? FormatNullableDateTimeValueCore(DateTime? value, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(DateTimeOffset value, CultureInfo? culture = null) => FormatDateTimeOffsetValueCore(value, format: null, culture);


        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="format">The format to use. Provided to <see cref="DateTimeOffset.ToString(string, IFormatProvider)"/>.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(DateTimeOffset value, string format, CultureInfo? culture = null) => FormatDateTimeOffsetValueCore(value, format, culture);

        private static string FormatDateTimeOffsetValueCore(DateTimeOffset value, string? format, CultureInfo? culture)
        {
            if (format != null)
            {
                return value.ToString(format, culture ?? CultureInfo.CurrentCulture);
            }

            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        private static string FormatDateTimeOffsetValueCore(DateTimeOffset value, CultureInfo? culture)
        {
            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(DateTimeOffset? value, CultureInfo? culture = null) => FormatNullableDateTimeOffsetValueCore(value, format: null, culture);

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="format">The format to use. Provided to <see cref="DateTimeOffset.ToString(string, IFormatProvider)"/>.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(DateTimeOffset? value, string format, CultureInfo? culture = null) => FormatNullableDateTimeOffsetValueCore(value, format, culture);

        private static string? FormatNullableDateTimeOffsetValueCore(DateTimeOffset? value, string? format, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            if (format != null)
            {
                return value.Value.ToString(format, culture ?? CultureInfo.CurrentCulture);
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        private static string? FormatNullableDateTimeOffsetValueCore(DateTimeOffset? value, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(DateOnly value, CultureInfo? culture = null) => FormatDateOnlyValueCore(value, format: null, culture);

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="format">The format to use. Provided to <see cref="DateOnly.ToString(string, IFormatProvider)"/>.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(DateOnly value, string format, CultureInfo? culture = null) => FormatDateOnlyValueCore(value, format, culture);

        private static string FormatDateOnlyValueCore(DateOnly value, string? format, CultureInfo? culture)
        {
            if (format != null)
            {
                // We convert to a DateTime so formatting doesn't throw if the format includes time information
                return value.ToDateTime(TimeOnly.MinValue).ToString(format, culture ?? CultureInfo.CurrentCulture);
            }

            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        private static string FormatDateOnlyValueCore(DateOnly value, CultureInfo? culture)
        {
            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(DateOnly? value, CultureInfo? culture = null) => FormatNullableDateOnlyValueCore(value, format: null, culture);

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="format">The format to use. Provided to <see cref="DateOnly.ToString(string, IFormatProvider)"/>.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(DateOnly? value, string format, CultureInfo? culture = null) => FormatNullableDateOnlyValueCore(value, format, culture);

        private static string? FormatNullableDateOnlyValueCore(DateOnly? value, string? format, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            if (format != null)
            {
                // We convert to a DateTime so formatting doesn't throw if the format includes time information
                return value.Value.ToDateTime(TimeOnly.MinValue).ToString(format, culture ?? CultureInfo.CurrentCulture);
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        private static string? FormatNullableDateOnlyValueCore(DateOnly? value, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(TimeOnly value, CultureInfo? culture = null) => FormatTimeOnlyValueCore(value, format: null, culture);

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="format">The format to use. Provided to <see cref="DateOnly.ToString(string, IFormatProvider)"/>.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string FormatValue(TimeOnly value, string format, CultureInfo? culture = null) => FormatTimeOnlyValueCore(value, format, culture);

        private static string FormatTimeOnlyValueCore(TimeOnly value, string? format, CultureInfo? culture)
        {
            if (format != null)
            {
                // We convert to a DateTime so formatting doesn't throw if the format includes date information
                return DateTime.MinValue.Add(value.ToTimeSpan()).ToString(format, culture ?? CultureInfo.CurrentCulture);
            }

            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        private static string FormatTimeOnlyValueCore(TimeOnly value, CultureInfo? culture)
        {
            return value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(TimeOnly? value, CultureInfo? culture = null) => FormatNullableTimeOnlyValueCore(value, format: null, culture);

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="format">The format to use. Provided to <see cref="DateOnly.ToString(string, IFormatProvider)"/>.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static string? FormatValue(TimeOnly? value, string format, CultureInfo? culture = null) => FormatNullableTimeOnlyValueCore(value, format, culture);

        private static string? FormatNullableTimeOnlyValueCore(TimeOnly? value, string? format, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            if (format != null)
            {
                // We convert to a DateTime so formatting doesn't throw if the format includes date information
                return DateTime.MinValue.Add(value.Value.ToTimeSpan()).ToString(format, culture ?? CultureInfo.CurrentCulture);
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        private static string? FormatNullableTimeOnlyValueCore(TimeOnly? value, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            return value.Value.ToString(culture ?? CultureInfo.CurrentCulture);
        }

        private static string? FormatEnumValueCore<T>(T value, CultureInfo? culture)
        {
            if (value == null)
            {
                return null;
            }

            return value.ToString();
        }

        /// <summary>
        /// Formats the provided <paramref name="value"/> as a <see cref="System.String"/>.
        /// </summary>
        /// <param name="value">The value to format.</param>
        /// <param name="culture">
        /// The <see cref="CultureInfo"/> to use while formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.
        /// </param>
        /// <returns>The formatted value.</returns>
        [SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required to maintain compatibility")]
        public static object? FormatValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(T value, CultureInfo? culture = null)
        {
            var formatter = FormatterDelegateCache.Get<T>();
            return formatter(value, culture);
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.String"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToString(object? obj, CultureInfo? culture, out string? value)
        {
            return ConvertToStringCore(obj, culture, out value);
        }

        internal static readonly BindParser<string?> ConvertToString = ConvertToStringCore;

        private static bool ConvertToStringCore(object? obj, CultureInfo? culture, out string? value)
        {
            // We expect the input to already be a string.
            value = (string?)obj;
            return true;
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.Boolean"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToBool(object? obj, CultureInfo? culture, out bool value)
        {
            return ConvertToBoolCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.Boolean"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableBool(object? obj, CultureInfo? culture, out bool? value)
        {
            return ConvertToNullableBoolCore(obj, culture, out value);
        }

        internal static readonly BindParser<bool> ConvertToBool = ConvertToBoolCore;
        internal static readonly BindParser<bool?> ConvertToNullableBool = ConvertToNullableBoolCore;

        private static bool ConvertToBoolCore(object? obj, CultureInfo? culture, out bool value)
        {
            // We expect the input to already be a bool.
            value = (bool)obj!;
            return true;
        }

        private static bool ConvertToNullableBoolCore(object? obj, CultureInfo? culture, out bool? value)
        {
            // We expect the input to already be a bool.
            value = (bool?)obj;
            return true;
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.Int32"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToInt(object? obj, CultureInfo? culture, out int value)
        {
            return ConvertToIntCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.Int32"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableInt(object? obj, CultureInfo? culture, out int? value)
        {
            return ConvertToNullableIntCore(obj, culture, out value);
        }

        internal static BindParser<int> ConvertToInt = ConvertToIntCore;
        internal static BindParser<int?> ConvertToNullableInt = ConvertToNullableIntCore;

        private static bool ConvertToIntCore(object? obj, CultureInfo? culture, out int value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return false;
            }

            if (!int.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        private static bool ConvertToNullableIntCore(object? obj, CultureInfo? culture, out int? value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return true;
            }

            if (!int.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.Int64"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToLong(object? obj, CultureInfo? culture, out long value)
        {
            return ConvertToLongCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.Int64"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableLong(object? obj, CultureInfo? culture, out long? value)
        {
            return ConvertToNullableLongCore(obj, culture, out value);
        }

        internal static BindParser<long> ConvertToLong = ConvertToLongCore;
        internal static BindParser<long?> ConvertToNullableLong = ConvertToNullableLongCore;

        private static bool ConvertToLongCore(object? obj, CultureInfo? culture, out long value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return false;
            }

            if (!long.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        private static bool ConvertToNullableLongCore(object? obj, CultureInfo? culture, out long? value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return true;
            }

            if (!long.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.Int16"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToShort(object? obj, CultureInfo? culture, out short value)
        {
            return ConvertToShortCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.Int16"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableShort(object? obj, CultureInfo? culture, out short? value)
        {
            return ConvertToNullableShort(obj, culture, out value);
        }

        internal static BindParser<short> ConvertToShort = ConvertToShortCore;
        internal static BindParser<short?> ConvertToNullableShort = ConvertToNullableShortCore;

        private static bool ConvertToShortCore(object? obj, CultureInfo? culture, out short value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return false;
            }

            if (!short.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        private static bool ConvertToNullableShortCore(object? obj, CultureInfo? culture, out short? value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return true;
            }

            if (!short.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.Single"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToFloat(object? obj, CultureInfo? culture, out float value)
        {
            return ConvertToFloatCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.Single"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableFloat(object? obj, CultureInfo? culture, out float? value)
        {
            return ConvertToNullableFloatCore(obj, culture, out value);
        }

        internal static BindParser<float> ConvertToFloat = ConvertToFloatCore;
        internal static BindParser<float?> ConvertToNullableFloat = ConvertToNullableFloatCore;

        private static bool ConvertToFloatCore(object? obj, CultureInfo? culture, out float value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return false;
            }

            if (!float.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            if (float.IsInfinity(converted) || float.IsNaN(converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        private static bool ConvertToNullableFloatCore(object? obj, CultureInfo? culture, out float? value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return true;
            }

            if (!float.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            if (float.IsInfinity(converted) || float.IsNaN(converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.Double"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToDouble(object? obj, CultureInfo? culture, out double value)
        {
            return ConvertToDoubleCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.Double"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableDouble(object? obj, CultureInfo? culture, out double? value)
        {
            return ConvertToNullableDoubleCore(obj, culture, out value);
        }

        internal static BindParser<double> ConvertToDoubleDelegate = ConvertToDoubleCore;
        internal static BindParser<double?> ConvertToNullableDoubleDelegate = ConvertToNullableDoubleCore;

        private static bool ConvertToDoubleCore(object? obj, CultureInfo? culture, out double value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return false;
            }

            if (!double.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            if (double.IsInfinity(converted) || double.IsNaN(converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        private static bool ConvertToNullableDoubleCore(object? obj, CultureInfo? culture, out double? value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return true;
            }

            if (!double.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            if (double.IsInfinity(converted) || double.IsNaN(converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.Decimal"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToDecimal(object? obj, CultureInfo? culture, out decimal value)
        {
            return ConvertToDecimalCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.Decimal"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableDecimal(object? obj, CultureInfo? culture, out decimal? value)
        {
            return ConvertToNullableDecimalCore(obj, culture, out value);
        }

        internal static BindParser<decimal> ConvertToDecimal = ConvertToDecimalCore;
        internal static BindParser<decimal?> ConvertToNullableDecimal = ConvertToNullableDecimalCore;

        private static bool ConvertToDecimalCore(object? obj, CultureInfo? culture, out decimal value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return false;
            }

            if (!decimal.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        private static bool ConvertToNullableDecimalCore(object? obj, CultureInfo? culture, out decimal? value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return true;
            }

            if (!decimal.TryParse(text, NumberStyles.Number, culture ?? CultureInfo.CurrentCulture, out var converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.DateTime"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToDateTime(object? obj, CultureInfo? culture, out DateTime value)
        {
            return ConvertToDateTimeCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.DateTime"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="format">The format string to use in conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToDateTime(object? obj, CultureInfo? culture, string format, out DateTime value)
        {
            return ConvertToDateTimeCore(obj, culture, format, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.DateTime"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableDateTime(object? obj, CultureInfo? culture, out DateTime? value)
        {
            return ConvertToNullableDateTimeCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.DateTime"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="format">The format string to use in conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableDateTime(object? obj, CultureInfo? culture, string format, out DateTime? value)
        {
            return ConvertToNullableDateTimeCore(obj, culture, format, out value);
        }

        internal static BindParser<DateTime> ConvertToDateTime = ConvertToDateTimeCore;
        internal static BindParserWithFormat<DateTime> ConvertToDateTimeWithFormat = ConvertToDateTimeCore;
        internal static BindParser<DateTime?> ConvertToNullableDateTime = ConvertToNullableDateTimeCore;
        internal static BindParserWithFormat<DateTime?> ConvertToNullableDateTimeWithFormat = ConvertToNullableDateTimeCore;

        private static bool ConvertToDateTimeCore(object? obj, CultureInfo? culture, out DateTime value)
        {
            return ConvertToDateTimeCore(obj, culture, format: null, out value);
        }

        private static bool ConvertToDateTimeCore(object? obj, CultureInfo? culture, string? format, out DateTime value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return false;
            }

            if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
            {
                value = converted;
                return true;
            }
            else if (format == null && DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
            {
                value = converted;
                return true;
            }

            value = default;
            return false;
        }

        private static bool ConvertToNullableDateTimeCore(object? obj, CultureInfo? culture, out DateTime? value)
        {
            return ConvertToNullableDateTimeCore(obj, culture, format: null, out value);
        }

        private static bool ConvertToNullableDateTimeCore(object? obj, CultureInfo? culture, string? format, out DateTime? value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return true;
            }

            if (format != null && DateTime.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
            {
                value = converted;
                return true;
            }
            else if (format == null && DateTime.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
            {
                value = converted;
                return true;
            }

            value = default;
            return false;
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.DateTimeOffset"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToDateTimeOffset(object? obj, CultureInfo? culture, out DateTimeOffset value)
        {
            return ConvertToDateTimeOffsetCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.DateTimeOffset"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="format">The format string to use in conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToDateTimeOffset(object? obj, CultureInfo? culture, string format, out DateTimeOffset value)
        {
            return ConvertToDateTimeOffsetCore(obj, culture, format, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.DateTimeOffset"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableDateTimeOffset(object? obj, CultureInfo? culture, out DateTimeOffset? value)
        {
            return ConvertToNullableDateTimeOffsetCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.DateTimeOffset"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="format">The format string to use in conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableDateTimeOffset(object? obj, CultureInfo? culture, string format, out DateTimeOffset? value)
        {
            return ConvertToNullableDateTimeOffsetCore(obj, culture, format, out value);
        }

        internal static BindParser<DateTimeOffset> ConvertToDateTimeOffset = ConvertToDateTimeOffsetCore;
        internal static BindParserWithFormat<DateTimeOffset> ConvertToDateTimeOffsetWithFormat = ConvertToDateTimeOffsetCore;
        internal static BindParser<DateTimeOffset?> ConvertToNullableDateTimeOffset = ConvertToNullableDateTimeOffsetCore;
        internal static BindParserWithFormat<DateTimeOffset?> ConvertToNullableDateTimeOffsetWithFormat = ConvertToNullableDateTimeOffsetCore;

        private static bool ConvertToDateTimeOffsetCore(object? obj, CultureInfo? culture, out DateTimeOffset value)
        {
            return ConvertToDateTimeOffsetCore(obj, culture, format: null, out value);
        }

        private static bool ConvertToDateTimeOffsetCore(object? obj, CultureInfo? culture, string? format, out DateTimeOffset value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return false;
            }

            if (format != null && DateTimeOffset.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
            {
                value = converted;
                return true;
            }
            else if (format == null && DateTimeOffset.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
            {
                value = converted;
                return true;
            }

            value = default;
            return false;
        }

        private static bool ConvertToNullableDateTimeOffsetCore(object? obj, CultureInfo? culture, out DateTimeOffset? value)
        {
            return ConvertToNullableDateTimeOffsetCore(obj, culture, format: null, out value);
        }

        private static bool ConvertToNullableDateTimeOffsetCore(object? obj, CultureInfo? culture, string? format, out DateTimeOffset? value)
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return true;
            }

            if (format != null && DateTimeOffset.TryParseExact(text, format, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out var converted))
            {
                value = converted;
                return true;
            }
            else if (format == null && DateTimeOffset.TryParse(text, culture ?? CultureInfo.CurrentCulture, DateTimeStyles.None, out converted))
            {
                value = converted;
                return true;
            }

            value = default;
            return false;
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.DateOnly"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToDateOnly(object? obj, CultureInfo? culture, out DateOnly value)
        {
            return ConvertToDateOnlyCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.DateOnly"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="format">The format string to use in conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToDateOnly(object? obj, CultureInfo? culture, string format, out DateOnly value)
        {
            return ConvertToDateOnlyCore(obj, culture, format, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.DateOnly"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableDateOnly(object? obj, CultureInfo? culture, out DateOnly? value)
        {
            return ConvertToNullableDateOnlyCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.DateOnly"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="format">The format string to use in conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableDateOnly(object? obj, CultureInfo? culture, string format, out DateOnly? value)
        {
            return ConvertToNullableDateOnlyCore(obj, culture, format, out value);
        }

        internal static BindParser<DateOnly> ConvertToDateOnly = ConvertToDateOnlyCore;
        internal static BindParserWithFormat<DateOnly> ConvertToDateOnlyWithFormat = ConvertToDateOnlyCore;
        internal static BindParser<DateOnly?> ConvertToNullableDateOnly = ConvertToNullableDateOnlyCore;
        internal static BindParserWithFormat<DateOnly?> ConvertToNullableDateOnlyWithFormat = ConvertToNullableDateOnlyCore;

        private static bool ConvertToDateOnlyCore(object? obj, CultureInfo? culture, out DateOnly value)
        {
            return ConvertToDateOnlyCore(obj, culture, format: null, out value);
        }

        private static bool ConvertToDateOnlyCore(object? obj, CultureInfo? culture, string? format, out DateOnly value)
        {
            // We first convert to a DateTime so conversion doesn't fail if time information is included
            if (ConvertToDateTimeCore(obj, culture, format, out var dateTime))
            {
                value = DateOnly.FromDateTime(dateTime);
                return true;
            }

            value = default;
            return false;
        }

        private static bool ConvertToNullableDateOnlyCore(object? obj, CultureInfo? culture, out DateOnly? value)
        {
            return ConvertToNullableDateOnlyCore(obj, culture, format: null, out value);
        }

        private static bool ConvertToNullableDateOnlyCore(object? obj, CultureInfo? culture, string? format, out DateOnly? value)
        {
            // We first convert to a DateTime so conversion doesn't fail if time information is included
            if (ConvertToDateTimeCore(obj, culture, format, out var dateTime))
            {
                value = DateOnly.FromDateTime(dateTime);
                return true;
            }

            value = default;
            return false;
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.TimeOnly"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToTimeOnly(object? obj, CultureInfo? culture, out TimeOnly value)
        {
            return ConvertToTimeOnlyCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a <see cref="System.TimeOnly"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="format">The format string to use in conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToTimeOnly(object? obj, CultureInfo? culture, string format, out TimeOnly value)
        {
            return ConvertToTimeOnlyCore(obj, culture, format, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.TimeOnly"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableTimeOnly(object? obj, CultureInfo? culture, out TimeOnly? value)
        {
            return ConvertToNullableTimeOnlyCore(obj, culture, out value);
        }

        /// <summary>
        /// Attempts to convert a value to a nullable <see cref="System.TimeOnly"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="format">The format string to use in conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertToNullableTimeOnly(object? obj, CultureInfo? culture, string format, out TimeOnly? value)
        {
            return ConvertToNullableTimeOnlyCore(obj, culture, format, out value);
        }

        internal static BindParser<TimeOnly> ConvertToTimeOnly = ConvertToTimeOnlyCore;
        internal static BindParserWithFormat<TimeOnly> ConvertToTimeOnlyWithFormat = ConvertToTimeOnlyCore;
        internal static BindParser<TimeOnly?> ConvertToNullableTimeOnly = ConvertToNullableTimeOnlyCore;
        internal static BindParserWithFormat<TimeOnly?> ConvertToNullableTimeOnlyWithFormat = ConvertToNullableTimeOnlyCore;

        private static bool ConvertToTimeOnlyCore(object? obj, CultureInfo? culture, out TimeOnly value)
        {
            return ConvertToTimeOnlyCore(obj, culture, format: null, out value);
        }

        private static bool ConvertToTimeOnlyCore(object? obj, CultureInfo? culture, string? format, out TimeOnly value)
        {
            // We first convert to a DateTime so conversion doesn't fail if time information is included
            if (ConvertToDateTimeCore(obj, culture, format, out var dateTime))
            {
                value = TimeOnly.FromDateTime(dateTime);
                return true;
            }

            value = default;
            return false;
        }

        private static bool ConvertToNullableTimeOnlyCore(object? obj, CultureInfo? culture, out TimeOnly? value)
        {
            return ConvertToNullableTimeOnlyCore(obj, culture, format: null, out value);
        }

        private static bool ConvertToNullableTimeOnlyCore(object? obj, CultureInfo? culture, string? format, out TimeOnly? value)
        {
            // We first convert to a DateTime so conversion doesn't fail if time information is included
            if (ConvertToDateTimeCore(obj, culture, format, out var dateTime))
            {
                value = TimeOnly.FromDateTime(dateTime);
                return true;
            }

            value = default;
            return false;
        }

        private static bool ConvertToEnum<T>(object? obj, CultureInfo? culture, out T value) where T : struct, Enum
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return true;
            }

            if (!Enum.TryParse<T>(text, out var converted))
            {
                value = default;
                return false;
            }

            if (!Enum.IsDefined(typeof(T), converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        private static bool ConvertToNullableEnum<T>(object? obj, CultureInfo? culture, out T? value) where T : struct, Enum
        {
            var text = (string?)obj;
            if (string.IsNullOrEmpty(text))
            {
                value = default;
                return true;
            }

            if (!Enum.TryParse<T>(text, out var converted))
            {
                value = default;
                return false;
            }

            if (!Enum.IsDefined(typeof(T), converted))
            {
                value = default;
                return false;
            }

            value = converted;
            return true;
        }

        /// <summary>
        /// Attempts to convert a value to a value of type <typeparamref name="T"/>.
        /// </summary>
        /// <param name="obj">The object to convert.</param>
        /// <param name="culture">The <see cref="CultureInfo"/> to use for conversion.</param>
        /// <param name="value">The converted value.</param>
        /// <returns><c>true</c> if conversion is successful, otherwise <c>false</c>.</returns>
        public static bool TryConvertTo<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(object? obj, CultureInfo? culture, [MaybeNullWhen(false)] out T value)
        {
            var converter = ParserDelegateCache.Get<T>();
            return converter(obj, culture, out value);
        }

        private static class FormatterDelegateCache
        {
            private static readonly ConcurrentDictionary<Type, Delegate> _cache = new ConcurrentDictionary<Type, Delegate>();

            private static MethodInfo? _makeArrayFormatter;

            [UnconditionalSuppressMessage(
                "ReflectionAnalysis",
                "IL2060:MakeGenericMethod",
                Justification = "The referenced methods don't have any DynamicallyAccessedMembers annotations. See https://github.com/mono/linker/issues/1727")]
            [UnconditionalSuppressMessage(
                "ReflectionAnalysis",
                "IL2075:MakeGenericMethod",
                Justification = "The referenced methods don't have any DynamicallyAccessedMembers annotations. See https://github.com/mono/linker/issues/1727")]
            public static BindFormatter<T> Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()
            {
                if (!_cache.TryGetValue(typeof(T), out var formatter))
                {
                    // We need to replicate all of the primitive cases that we handle here so that they will behave the same way.
                    // The result will be cached.
                    if (typeof(T) == typeof(string))
                    {
                        formatter = (BindFormatter<string>)FormatStringValueCore;
                    }
                    else if (typeof(T) == typeof(bool))
                    {
                        formatter = (BindFormatter<bool>)FormatBoolValueCore;
                    }
                    else if (typeof(T) == typeof(bool?))
                    {
                        formatter = (BindFormatter<bool?>)FormatNullableBoolValueCore;
                    }
                    else if (typeof(T) == typeof(int))
                    {
                        formatter = (BindFormatter<int>)FormatIntValueCore;
                    }
                    else if (typeof(T) == typeof(int?))
                    {
                        formatter = (BindFormatter<int?>)FormatNullableIntValueCore;
                    }
                    else if (typeof(T) == typeof(long))
                    {
                        formatter = (BindFormatter<long>)FormatLongValueCore;
                    }
                    else if (typeof(T) == typeof(long?))
                    {
                        formatter = (BindFormatter<long?>)FormatNullableLongValueCore;
                    }
                    else if (typeof(T) == typeof(short))
                    {
                        formatter = (BindFormatter<short>)FormatShortValueCore;
                    }
                    else if (typeof(T) == typeof(short?))
                    {
                        formatter = (BindFormatter<short?>)FormatNullableShortValueCore;
                    }
                    else if (typeof(T) == typeof(float))
                    {
                        formatter = (BindFormatter<float>)FormatFloatValueCore;
                    }
                    else if (typeof(T) == typeof(float?))
                    {
                        formatter = (BindFormatter<float?>)FormatNullableFloatValueCore;
                    }
                    else if (typeof(T) == typeof(double))
                    {
                        formatter = (BindFormatter<double>)FormatDoubleValueCore;
                    }
                    else if (typeof(T) == typeof(double?))
                    {
                        formatter = (BindFormatter<double?>)FormatNullableDoubleValueCore;
                    }
                    else if (typeof(T) == typeof(decimal))
                    {
                        formatter = (BindFormatter<decimal>)FormatDecimalValueCore;
                    }
                    else if (typeof(T) == typeof(decimal?))
                    {
                        formatter = (BindFormatter<decimal?>)FormatNullableDecimalValueCore;
                    }
                    else if (typeof(T) == typeof(DateTime))
                    {
                        formatter = (BindFormatter<DateTime>)FormatDateTimeValueCore;
                    }
                    else if (typeof(T) == typeof(DateTime?))
                    {
                        formatter = (BindFormatter<DateTime?>)FormatNullableDateTimeValueCore;
                    }
                    else if (typeof(T) == typeof(DateTimeOffset))
                    {
                        formatter = (BindFormatter<DateTimeOffset>)FormatDateTimeOffsetValueCore;
                    }
                    else if (typeof(T) == typeof(DateTimeOffset?))
                    {
                        formatter = (BindFormatter<DateTimeOffset?>)FormatNullableDateTimeOffsetValueCore;
                    }
                    else if (typeof(T) == typeof(DateOnly))
                    {
                        formatter = (BindFormatter<DateOnly>)FormatDateOnlyValueCore;
                    }
                    else if (typeof(T) == typeof(DateOnly?))
                    {
                        formatter = (BindFormatter<DateOnly?>)FormatNullableDateOnlyValueCore;
                    }
                    else if (typeof(T) == typeof(TimeOnly))
                    {
                        formatter = (BindFormatter<TimeOnly>)FormatTimeOnlyValueCore;
                    }
                    else if (typeof(T) == typeof(TimeOnly?))
                    {
                        formatter = (BindFormatter<TimeOnly?>)FormatNullableTimeOnlyValueCore;
                    }
                    else if (typeof(T).IsEnum || Nullable.GetUnderlyingType(typeof(T)) is Type { IsEnum: true } innerType)
                    {
                        formatter = (BindFormatter<T>)FormatEnumValueCore<T>;
                    }
                    else if (typeof(T).IsArray)
                    {
                        var method = _makeArrayFormatter ??= typeof(FormatterDelegateCache).GetMethod(nameof(MakeArrayFormatter), BindingFlags.NonPublic | BindingFlags.Static)!;
                        var elementType = typeof(T).GetElementType()!;
                        formatter = (Delegate)method.MakeGenericMethod(elementType).Invoke(null, null)!;
                    }
                    else
                    {
                        formatter = MakeTypeConverterFormatter<T>();
                    }

                    _cache.TryAdd(typeof(T), formatter);
                }

                return (BindFormatter<T>)formatter;
            }

            private static BindFormatter<T[]> MakeArrayFormatter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()
            {
                var elementFormatter = Get<T>();

                return FormatArrayValue;

                string? FormatArrayValue(T[] value, CultureInfo? culture)
                {
                    if (value.Length == 0)
                    {
                        return "[]";
                    }

                    var builder = new StringBuilder("[\"");
                    builder.Append(JsonEncodedText.Encode(elementFormatter(value[0], culture)?.ToString() ?? string.Empty));
                    builder.Append('\"');

                    for (var i = 1; i < value.Length; i++)
                    {
                        builder.Append(", \"");
                        builder.Append(JsonEncodedText.Encode(elementFormatter(value[i], culture)?.ToString() ?? string.Empty));
                        builder.Append('\"');
                    }

                    builder.Append(']');

                    return builder.ToString();
                }
            }

            private static BindFormatter<T> MakeTypeConverterFormatter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()
            {
                var typeConverter = TypeDescriptor.GetConverter(typeof(T));
                if (typeConverter == null || !typeConverter.CanConvertTo(typeof(string)))
                {
                    throw new InvalidOperationException(
                        $"The type '{typeof(T).FullName}' does not have an associated {typeof(TypeConverter).Name} that supports " +
                        $"conversion to a string. " +
                        $"Apply '{typeof(TypeConverterAttribute).Name}' to the type to register a converter.");
                }

                return FormatWithTypeConverter;

                string? FormatWithTypeConverter(T value, CultureInfo? culture)
                {
                    // We intentionally close-over the TypeConverter to cache it. The TypeDescriptor infrastructure is slow.
                    return typeConverter.ConvertToString(context: null, culture ?? CultureInfo.CurrentCulture, value);
                }
            }
        }

        internal static class ParserDelegateCache
        {
            private static readonly ConcurrentDictionary<Type, Delegate> _cache = new ConcurrentDictionary<Type, Delegate>();

            private static MethodInfo? _convertToEnum;
            private static MethodInfo? _convertToNullableEnum;
            private static MethodInfo? _makeArrayTypeConverter;

            [UnconditionalSuppressMessage(
                "ReflectionAnalysis",
                "IL2060:MakeGenericMethod",
                Justification = "The referenced methods don't have any DynamicallyAccessedMembers annotations. See https://github.com/mono/linker/issues/1727")]
            [UnconditionalSuppressMessage(
                "ReflectionAnalysis",
                "IL2075:MakeGenericMethod",
                Justification = "The referenced methods don't have any DynamicallyAccessedMembers annotations. See https://github.com/mono/linker/issues/1727")]
            public static BindParser<T> Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()
            {
                if (!_cache.TryGetValue(typeof(T), out var parser))
                {
                    // We need to replicate all of the primitive cases that we handle here so that they will behave the same way.
                    // The result will be cached.
                    if (typeof(T) == typeof(string))
                    {
                        parser = ConvertToString;
                    }
                    else if (typeof(T) == typeof(bool))
                    {
                        parser = ConvertToBool;
                    }
                    else if (typeof(T) == typeof(bool?))
                    {
                        parser = ConvertToNullableBool;
                    }
                    else if (typeof(T) == typeof(int))
                    {
                        parser = ConvertToInt;
                    }
                    else if (typeof(T) == typeof(int?))
                    {
                        parser = ConvertToNullableInt;
                    }
                    else if (typeof(T) == typeof(long))
                    {
                        parser = ConvertToLong;
                    }
                    else if (typeof(T) == typeof(long?))
                    {
                        parser = ConvertToNullableLong;
                    }
                    else if (typeof(T) == typeof(short))
                    {
                        parser = ConvertToShort;
                    }
                    else if (typeof(T) == typeof(short?))
                    {
                        parser = ConvertToNullableShort;
                    }
                    else if (typeof(T) == typeof(float))
                    {
                        parser = ConvertToFloat;
                    }
                    else if (typeof(T) == typeof(float?))
                    {
                        parser = ConvertToNullableFloat;
                    }
                    else if (typeof(T) == typeof(double))
                    {
                        parser = ConvertToDoubleDelegate;
                    }
                    else if (typeof(T) == typeof(double?))
                    {
                        parser = ConvertToNullableDoubleDelegate;
                    }
                    else if (typeof(T) == typeof(decimal))
                    {
                        parser = ConvertToDecimal;
                    }
                    else if (typeof(T) == typeof(decimal?))
                    {
                        parser = ConvertToNullableDecimal;
                    }
                    else if (typeof(T) == typeof(DateTime))
                    {
                        parser = ConvertToDateTime;
                    }
                    else if (typeof(T) == typeof(DateTime?))
                    {
                        parser = ConvertToNullableDateTime;
                    }
                    else if (typeof(T) == typeof(DateTimeOffset))
                    {
                        parser = ConvertToDateTimeOffset;
                    }
                    else if (typeof(T) == typeof(DateTimeOffset?))
                    {
                        parser = ConvertToNullableDateTimeOffset;
                    }
                    else if (typeof(T) == typeof(DateOnly))
                    {
                        parser = ConvertToDateOnly;
                    }
                    else if (typeof(T) == typeof(DateOnly?))
                    {
                        parser = ConvertToNullableDateOnly;
                    }
                    else if (typeof(T) == typeof(TimeOnly))
                    {
                        parser = ConvertToTimeOnly;
                    }
                    else if (typeof(T) == typeof(TimeOnly?))
                    {
                        parser = ConvertToNullableTimeOnly;
                    }
                    else if (typeof(T).IsEnum)
                    {
                        // We have to deal invoke this dynamically to work around the type constraint on Enum.TryParse.
                        var method = _convertToEnum ??= typeof(BindConverter).GetMethod(nameof(ConvertToEnum), BindingFlags.NonPublic | BindingFlags.Static)!;
                        parser = method.MakeGenericMethod(typeof(T)).CreateDelegate(typeof(BindParser<T>), target: null);
                    }
                    else if (Nullable.GetUnderlyingType(typeof(T)) is Type innerType && innerType.IsEnum)
                    {
                        // We have to deal invoke this dynamically to work around the type constraint on Enum.TryParse.
                        var method = _convertToNullableEnum ??= typeof(BindConverter).GetMethod(nameof(ConvertToNullableEnum), BindingFlags.NonPublic | BindingFlags.Static)!;
                        parser = method.MakeGenericMethod(innerType).CreateDelegate(typeof(BindParser<T>), target: null);
                    }
                    else if (typeof(T).IsArray)
                    {
                        var method = _makeArrayTypeConverter ??= typeof(ParserDelegateCache).GetMethod(nameof(MakeArrayTypeConverter), BindingFlags.NonPublic | BindingFlags.Static)!;
                        var elementType = typeof(T).GetElementType()!;
                        parser = (Delegate)method.MakeGenericMethod(elementType).Invoke(null, null)!;
                    }
                    else
                    {
                        parser = MakeTypeConverterConverter<T>();
                    }

                    _cache.TryAdd(typeof(T), parser);
                }

                return (BindParser<T>)parser;
            }

            private static BindParser<T[]?> MakeArrayTypeConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()
            {
                var elementParser = Get<T>();

                return ConvertToArray;

                bool ConvertToArray(object? obj, CultureInfo? culture, out T[]? value)
                {
                    if (obj is not Array initialArray)
                    {
                        value = default;
                        return false;
                    }

                    var convertedArray = new T[initialArray.Length];

                    for (var i = 0; i < initialArray.Length; i++)
                    {
                        if (!elementParser(initialArray.GetValue(i), culture, out convertedArray[i]!))
                        {
                            value = default;
                            return false;
                        }
                    }

                    value = convertedArray;
                    return true;
                }
            }

            private static BindParser<T> MakeTypeConverterConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()
            {
                var typeConverter = TypeDescriptor.GetConverter(typeof(T));
                if (typeConverter == null || !typeConverter.CanConvertFrom(typeof(string)))
                {
                    throw new InvalidOperationException(
                        $"The type '{typeof(T).FullName}' does not have an associated {typeof(TypeConverter).Name} that supports " +
                        $"conversion from a string. " +
                        $"Apply '{typeof(TypeConverterAttribute).Name}' to the type to register a converter.");
                }

                return ConvertWithTypeConverter;

                bool ConvertWithTypeConverter(object? obj, CultureInfo? culture, out T value)
                {
                    // We intentionally close-over the TypeConverter to cache it. The TypeDescriptor infrastructure is slow.
                    if (obj == null)
                    {
                        value = default!;
                        return true;
                    }
                    var converted = typeConverter.ConvertFrom(context: null, culture ?? CultureInfo.CurrentCulture, obj);
                    if (converted == null)
                    {
                        value = default!;
                        return true;
                    }

                    value = (T)converted;
                    return true;
                }
            }
        }
    }
}
