<commit_before>using CK.Text;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace CK.CodeGen
{
    /// <summary>
    /// Full immutable representation of a Nullable Type with its <see cref="RawSubTypes"/>.
    /// This does not capture nesting/enclosing generic types: this can be computed only for top level types or types nested in non generic types.
    /// <para>
    /// The extension method <see cref="NullabilityTypeExtensions.GetNullableTypeTree(Type, NullabilityTypeInfo)"/> is the factory method to use
    /// to obtain this detailed information...
    /// </para>
    /// <para>
    /// ...or one of the other extension methods that first obtain the <see cref="NullabilityTypeInfo"/>:
    /// <see cref="NullabilityTypeExtensions.GetNullableTypeTree(System.Reflection.PropertyInfo)"/>, <see cref="NullabilityTypeExtensions.GetNullableTypeTree(System.Reflection.ParameterInfo)"/>
    /// or <see cref="NullabilityTypeExtensions.GetNullableTypeTree(System.Reflection.FieldInfo)"/>.
    /// </para>
    /// <para>
    /// When no Nullable Reference Type (NRT) context is available (oblivious context), the basic <see cref="NullabilityTypeExtensions.GetNullableTypeTree(Type)"/>
    /// can be used.
    /// </para>
    /// </summary>
    public readonly struct NullableTypeTree : IEquatable<NullableTypeTree>
    {
        readonly NullableTypeTree[] _rawSubTypes;

        class FixDictionaryBuilder : INullableTypeTreeBuilder
        {
            public static readonly INullableTypeTreeBuilder Instance = new FixDictionaryBuilder();

            public NullableTypeTree Create( Type t, NullabilityTypeKind kind, NullableTypeTree[] subTypes, Type[]? genericArguments = null )
            {
                if( genericArguments?.Length == 2 )
                {
                    var tGen = t.GetGenericTypeDefinition();
                    if( tGen == typeof( IDictionary<,> ) || tGen == typeof( Dictionary<,> ) )
                    {
                        var tKey = subTypes[0];
                        if( tKey.Kind.IsNullable() && tKey.Kind.IsReferenceType() )
                        {
                            subTypes[0] = tKey.ToAbnormalNull();
                        }
                    }
                }
                return new NullableTypeTree( t, kind, subTypes );
            }
        }

        /// <summary>
        /// Gets or sets a builder that will be used by <see cref="NullabilityTypeExtensions.GetNullableTypeTree(Type, INullableTypeTreeBuilder?)"/>
        /// when no other builder is specified.
        /// This defaults to a builder that ensures that the key of <see cref="Dictionary{TKey, TValue}"/> and <see cref="IDictionary{TKey, TValue}"/>
        /// is not nullable.
        /// </summary>
        public static INullableTypeTreeBuilder ObliviousDefaultBuilder { get; set; } = FixDictionaryBuilder.Instance;

        /// <summary>
        /// Gets the type.
        /// When this <see cref="Kind"/> is a <see cref="NullablityTypeKindExtension.IsNullableValueType(NullabilityTypeKind)"/> (a <see cref="Nullable{T}"/>),
        /// then this type is the inner type, not the Nullable generic type.
        /// </summary>
        public Type Type { get; }

        /// <summary>
        /// The <see cref="NullabilityTypeKind"/> for this <see cref="Type"/>.
        /// </summary>
        public NullabilityTypeKind Kind { get; }

        /// <summary>
        /// The subordinates types if any. Can be generic parameters of the <see cref="Type"/> or the item type of an array.
        /// This "raw" types are the direct children: for <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> only
        /// the 8 types appear (including the last value tuple that can be a singleton).
        /// </summary>
        public IReadOnlyList<NullableTypeTree> RawSubTypes => _rawSubTypes;

        /// <summary>
        /// The subordinates types if any. This flattens <see cref="RawSubTypes"/> if <see cref="IsLongValueTuple"/> is true.
        /// </summary>
        public readonly IEnumerable<NullableTypeTree> SubTypes => IsLongValueTuple
                                                                    ? RawSubTypes.Take( 7 ).Concat( RawSubTypes[7].SubTypes )
                                                                    : RawSubTypes;

        /// <summary>
        /// Gets whether this is a <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/>: TRest is a value tuple that
        /// is the singleton <see cref="ValueTuple{T}"/> when the actual value tuple contains exactly 8 parameters.
        /// </summary>
        public bool IsLongValueTuple => Kind.IsTupleType() && RawSubTypes.Count == 8;

        /// <summary>
        /// Gets whether this top <see cref="Type"/> is a "normal null".
        /// We consider "Null Normality" as simply being <c>Kind.IsReferenceType() == Kind.IsNullable()</c>: a reference type
        /// is "normally nullable" and a value type is "normally not nullable".
        /// <para>
        /// This acts as a normalized form that can avoid doubling a cache of <see cref="NullableTypeTree"/> information when
        /// the cached information is easily computed to the opposite "not normally nullable" form (like adding or removing a '?'
        /// after a type name).
        /// </para>
        /// </summary>
        public bool IsNormalNull => Kind.IsReferenceType() == Kind.IsNullable();

        /// <summary>
        /// Returns this full <see cref="NullableTypeTree"/> with its top <see cref="Type"/> in <see cref="IsNormalNull"/> form:
        /// <list type="bullet">
        /// <item>
        /// If this is a nullable reference type or a non nullable value type, this is returned unchanged.
        /// </item>
        /// <item>
        /// If this is a non-nullable reference type, a new tree with the same <see cref="Type"/> and a <see cref="NullabilityTypeKind.IsNullable"/> kind
        /// is returned.
        /// </item>
        /// <item>
        /// If this is a nullable value type, a new tree with the (already not nullable) <see cref="Type"/> and a non <see cref="NullabilityTypeKind.IsNullable"/> kind
        /// is returned.
        /// </item>
        /// </list>
        /// This doesn't change the type tree in depth, only this top Type/Kind is concerned by this normalization.
        /// </summary>
        /// <returns>This or a normalized tree.</returns>
        public NullableTypeTree ToNormalNull()
        {
            if( Kind.IsReferenceType() )
            {
                return Kind.IsNullable()
                        ? this
                        : new NullableTypeTree( Type, Kind | NullabilityTypeKind.IsNullable, _rawSubTypes );
            }
            return Kind.IsNullable()
                    ? new NullableTypeTree( Type, Kind & ~(NullabilityTypeKind.IsNullable | NullabilityTypeKind.IsTechnicallyNullable), _rawSubTypes )
                    : this;
        }

        /// <summary>
        /// Mirror of <see cref="ToNormalNull"/> with the same limitation: this doesn't change the type tree in depth,
        /// only this top Type/Kind is concerned by this normalization.
        /// </summary>
        /// <returns></returns>
        public NullableTypeTree ToAbnormalNull()
        {
            if( Kind.IsReferenceType() )
            {
                return Kind.IsNullable()
                        ? new NullableTypeTree( Type, Kind & ~NullabilityTypeKind.IsNullable, _rawSubTypes )
                        : this;
            }
            return Kind.IsNullable()
                    ? this
                    : new NullableTypeTree( Type, Kind | NullabilityTypeKind.IsNullable | NullabilityTypeKind.IsTechnicallyNullable, _rawSubTypes );
        }

        /// <summary>
        /// Returns a <see cref="NullableTypeTree"/> with a changed primary <see cref="Type"/> but
        /// with exactly the same <see cref="Kind"/> and <see cref="RawSubTypes"/> as this one.
        /// This must be used with care (no check is done on the changed type): this is typically useful
        /// to change a <see cref="IList{T}"/> to a <see cref="List{T}"/> and preserve the nullability
        /// information.
        /// </summary>
        /// <param name="t">The new type that must be coherent with this one otherwise the behavior is undefined.</param>
        /// <returns>A new tree with the same nullability information.</returns>
        public NullableTypeTree WithType( Type t )
        {
            if( t == null ) throw new ArgumentNullException( nameof( t ) );
            return new NullableTypeTree( t, Kind, _rawSubTypes );
        }

        /// <summary>
        /// Returns a <see cref="NullableTypeTree"/> with a changed (or not) subordinated <see cref="SubTypes"/> but
        /// always with exactly the same <see cref="Kind"/> and <see cref="Type"/> as this one.
        /// This must be used with care (no check is done on the updated subordinated type).
        /// </summary>
        /// <param name="idx">Index in the <see cref="SubTypes"/> that must be set.</param>
        /// <param name="newOne">The new subordinated type.</param>
        /// <returns>A new tree with a true flag or the same tree with a false flag.</returns>
        /// <remarks><commit_msg>With supersedes WithType (allowing Kind update).<commit_after>using CK.Text;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace CK.CodeGen
{
    /// <summary>
    /// Full immutable representation of a Nullable Type with its <see cref="RawSubTypes"/>.
    /// This does not capture nesting/enclosing generic types: this can be computed only for top level types or types nested in non generic types.
    /// <para>
    /// The extension method <see cref="NullabilityTypeExtensions.GetNullableTypeTree(Type, NullabilityTypeInfo)"/> is the factory method to use
    /// to obtain this detailed information...
    /// </para>
    /// <para>
    /// ...or one of the other extension methods that first obtain the <see cref="NullabilityTypeInfo"/>:
    /// <see cref="NullabilityTypeExtensions.GetNullableTypeTree(System.Reflection.PropertyInfo)"/>, <see cref="NullabilityTypeExtensions.GetNullableTypeTree(System.Reflection.ParameterInfo)"/>
    /// or <see cref="NullabilityTypeExtensions.GetNullableTypeTree(System.Reflection.FieldInfo)"/>.
    /// </para>
    /// <para>
    /// When no Nullable Reference Type (NRT) context is available (oblivious context), the basic <see cref="NullabilityTypeExtensions.GetNullableTypeTree(Type)"/>
    /// can be used.
    /// </para>
    /// </summary>
    public readonly struct NullableTypeTree : IEquatable<NullableTypeTree>
    {
        readonly NullableTypeTree[] _rawSubTypes;

        class FixDictionaryBuilder : INullableTypeTreeBuilder
        {
            public static readonly INullableTypeTreeBuilder Instance = new FixDictionaryBuilder();

            public NullableTypeTree Create( Type t, NullabilityTypeKind kind, NullableTypeTree[] subTypes, Type[]? genericArguments = null )
            {
                if( genericArguments?.Length == 2 )
                {
                    var tGen = t.GetGenericTypeDefinition();
                    if( tGen == typeof( IDictionary<,> ) || tGen == typeof( Dictionary<,> ) )
                    {
                        var tKey = subTypes[0];
                        if( tKey.Kind.IsNullable() && tKey.Kind.IsReferenceType() )
                        {
                            subTypes[0] = tKey.ToAbnormalNull();
                        }
                    }
                }
                return new NullableTypeTree( t, kind, subTypes );
            }
        }

        /// <summary>
        /// Gets or sets a builder that will be used by <see cref="NullabilityTypeExtensions.GetNullableTypeTree(Type, INullableTypeTreeBuilder?)"/>
        /// when no other builder is specified.
        /// This defaults to a builder that ensures that the key of <see cref="Dictionary{TKey, TValue}"/> and <see cref="IDictionary{TKey, TValue}"/>
        /// is not nullable.
        /// </summary>
        public static INullableTypeTreeBuilder ObliviousDefaultBuilder { get; set; } = FixDictionaryBuilder.Instance;

        /// <summary>
        /// Gets the type.
        /// When this <see cref="Kind"/> is a <see cref="NullablityTypeKindExtension.IsNullableValueType(NullabilityTypeKind)"/> (a <see cref="Nullable{T}"/>),
        /// then this type is the inner type, not the Nullable generic type.
        /// </summary>
        public Type Type { get; }

        /// <summary>
        /// The <see cref="NullabilityTypeKind"/> for this <see cref="Type"/>.
        /// </summary>
        public NullabilityTypeKind Kind { get; }

        /// <summary>
        /// The subordinates types if any. Can be generic parameters of the <see cref="Type"/> or the item type of an array.
        /// This "raw" types are the direct children: for <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/> only
        /// the 8 types appear (including the last value tuple that can be a singleton).
        /// </summary>
        public IReadOnlyList<NullableTypeTree> RawSubTypes => _rawSubTypes;

        /// <summary>
        /// The subordinates types if any. This flattens <see cref="RawSubTypes"/> if <see cref="IsLongValueTuple"/> is true.
        /// </summary>
        public readonly IEnumerable<NullableTypeTree> SubTypes => IsLongValueTuple
                                                                    ? RawSubTypes.Take( 7 ).Concat( RawSubTypes[7].SubTypes )
                                                                    : RawSubTypes;

        /// <summary>
        /// Gets whether this is a <see cref="ValueTuple{T1, T2, T3, T4, T5, T6, T7, TRest}"/>: TRest is a value tuple that
        /// is the singleton <see cref="ValueTuple{T}"/> when the actual value tuple contains exactly 8 parameters.
        /// </summary>
        public bool IsLongValueTuple => Kind.IsTupleType() && RawSubTypes.Count == 8;

        /// <summary>
        /// Gets whether this top <see cref="Type"/> is a "normal null".
        /// We consider "Null Normality" as simply being <c>Kind.IsReferenceType() == Kind.IsNullable()</c>: a reference type
        /// is "normally nullable" and a value type is "normally not nullable".
        /// <para>
        /// This acts as a normalized form that can avoid doubling a cache of <see cref="NullableTypeTree"/> information when
        /// the cached information is easily computed to the opposite "not normally nullable" form (like adding or removing a '?'
        /// after a type name).
        /// </para>
        /// </summary>
        public bool IsNormalNull => Kind.IsReferenceType() == Kind.IsNullable();

        /// <summary>
        /// Returns this full <see cref="NullableTypeTree"/> with its top <see cref="Type"/> in <see cref="IsNormalNull"/> form:
        /// <list type="bullet">
        /// <item>
        /// If this is a nullable reference type or a non nullable value type, this is returned unchanged.
        /// </item>
        /// <item>
        /// If this is a non-nullable reference type, a new tree with the same <see cref="Type"/> and a <see cref="NullabilityTypeKind.IsNullable"/> kind
        /// is returned.
        /// </item>
        /// <item>
        /// If this is a nullable value type, a new tree with the (already not nullable) <see cref="Type"/> and a non <see cref="NullabilityTypeKind.IsNullable"/> kind
        /// is returned.
        /// </item>
        /// </list>
        /// This doesn't change the type tree in depth, only this top Type/Kind is concerned by this normalization.
        /// </summary>
        /// <returns>This or a normalized tree.</returns>
        public NullableTypeTree ToNormalNull()
        {
            if( Kind.IsReferenceType() )
            {
                return Kind.IsNullable()
                        ? this
                        : new NullableTypeTree( Type, Kind | NullabilityTypeKind.IsNullable, _rawSubTypes );
            }
            return Kind.IsNullable()
                    ? new NullableTypeTree( Type, Kind & ~(NullabilityTypeKind.IsNullable | NullabilityTypeKind.IsTechnicallyNullable), _rawSubTypes )
                    : this;
        }

        /// <summary>
        /// Mirror of <see cref="ToNormalNull"/> with the same limitation: this doesn't change the type tree in depth,
        /// only this top Type/Kind is concerned by this normalization.
        /// </summary>
        /// <returns></returns>
        public NullableTypeTree ToAbnormalNull()
        {
            if( Kind.IsReferenceType() )
            {
                return Kind.IsNullable()
                        ? new NullableTypeTree( Type, Kind & ~NullabilityTypeKind.IsNullable, _rawSubTypes )
                        : this;
            }
            return Kind.IsNullable()
                    ? this
                    : new NullableTypeTree( Type, Kind | NullabilityTypeKind.IsNullable | NullabilityTypeKind.IsTechnicallyNullable, _rawSubTypes );
        }

        /// <summary>
        /// Returns a <see cref="NullableTypeTree"/> with a changed primary <see cref="Type"/> and/or <see cref="Kind"/> but
        /// with exactly the same <see cref="RawSubTypes"/> as this one.
        /// This must be used with care (no check is done on the changed type): this is typically useful
        /// to change a <see cref="IList{T}"/> to a <see cref="List{T}"/> and preserve the nullability
        /// information.
        /// </summary>
        /// <param name="newType">The new type (when not null).</param>
        /// <param name="newKind">The new kind  (when not null).</param>
        /// <returns>A new tree with the same nullability information.</returns>
        public NullableTypeTree With( Type? newType = null, NullabilityTypeKind? newKind = null )
        {
            return new NullableTypeTree( newType ?? Type, newKind ?? Kind, _rawSubTypes );
        }

        /// <summary>
        /// Returns a <see cref="NullableTypeTree"/> with a changed (or not) subordinated <see cref="SubTypes"/> but
        /// always with exactly the same <see cref="Kind"/> and <see cref="Type"/> as this one.
        /// This must be used with care (no check is done on the updated subordinated type).
        /// </summary>
        /// <param name="idx">Index in the <see cref="SubTypes"/> that must be set.</param>
        /// <param name="newOne">The new subordinated type.</param>
        /// <returns>A new tree with a true flag or the same tree with a false flag.</returns>
        /// <remarks>
{'fraction_non_alphanumeric': 0.119861370301253, 'fraction_numerical': 0.002346041055718475, 'mean_word_length': 2.02077629247866, 'pattern_counts': {'":': 0, '<': 213, '<?xml version=': 0, '>': 221, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '358694', 'n_tokens_mistral': 4810, 'n_tokens_neox': 4698, 'n_words': 1660}