using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;

namespace AyCode.Core.Serializers.SourceGenerator;

/// <summary>
/// Type-analysis utilities for the AcBinary source generator: kind detection, FNV-1a hashing,
/// symbol enumeration, name flattening, and recursive scan-need computation. All methods are
/// pure functions over Roslyn symbols (no mutable state, safe to call from any emit pass).
/// </summary>
public partial class AcBinarySourceGenerator
{
    /// <summary>
    /// Returns true for property types that use markerless serialization in FastMode.
    /// These types have ExpectedTypeCode at runtime — no type marker byte, no PropertySkip for defaults.
    /// </summary>
    private static bool IsMarkerless(PropertyTypeKind k) => k switch
    {
        PropertyTypeKind.Int32 or PropertyTypeKind.Int64 or PropertyTypeKind.Int16 or
        PropertyTypeKind.Byte or PropertyTypeKind.UInt16 or PropertyTypeKind.UInt32 or PropertyTypeKind.UInt64 or
        PropertyTypeKind.Double or PropertyTypeKind.Single or PropertyTypeKind.Decimal or
        PropertyTypeKind.DateTime or PropertyTypeKind.Guid or
        PropertyTypeKind.TimeSpan or PropertyTypeKind.DateTimeOffset or
        PropertyTypeKind.Boolean or PropertyTypeKind.Enum => true,
        _ => false
    };

    /// <summary>
    /// Builds a flat class name for nested types: Outer_Inner_Leaf.
    /// For top-level types returns the simple name unchanged.
    /// </summary>
    private static string BuildFlatName(INamedTypeSymbol typeSymbol)
    {
        if (typeSymbol.ContainingType == null)
            return typeSymbol.Name;

        var parts = new List<string>();
        var current = typeSymbol;
        while (current != null)
        {
            parts.Add(current.Name);
            current = current.ContainingType;
        }
        parts.Reverse();
        return string.Join("_", parts);
    }

    /// <summary>
    /// Reads EnableMetadataFeature from a type's [AcBinarySerializable] attribute.
    /// Returns true (default) if no attribute or enableAllFeatures=true.
    /// </summary>
    private static bool ReadEnableMetadata(ITypeSymbol type)
    {
        var attr = type.GetAttributes().FirstOrDefault(a =>
            a.AttributeClass?.ToDisplayString() == AttributeName);
        if (attr == null) return true;
        if (attr.ConstructorArguments.Length == 1)
            return (bool)attr.ConstructorArguments[0].Value!;
        if (attr.ConstructorArguments.Length == 4)
            return (bool)attr.ConstructorArguments[0].Value!;
        return true;
    }

    /// <summary>
    /// Computes whether a type needs scan pass work, split into ref tracking and string interning.
    /// Uses a per-call HashSet to guard against circular references (no static cache —
    /// static state is unsafe in incremental generators as it persists across builds).
    /// Returns (needsRefScan, needsInternScan) — these are independent axes.
    /// </summary>
    private static (bool needsIdScan, bool needsAllRefScan, bool needsInternScan) ComputeNeedsScan(ITypeSymbol type)
    {
        return ComputeNeedsScanCore(type, new HashSet<string>());
    }

    private static (bool needsIdScan, bool needsAllRefScan, bool needsInternScan) ComputeNeedsScanCore(ITypeSymbol type, HashSet<string> visiting)
    {
        // Circular reference guard: if already visiting this type, assume true (safe fallback)
        var key = type.ToDisplayString();
        if (!visiting.Add(key))
            return (true, true, true);

        // Read [AcBinarySerializable] flags
        var attr = type.GetAttributes().FirstOrDefault(a =>
            a.AttributeClass?.ToDisplayString() == AttributeName);

        bool enableIdTracking = true, enableRefHandling = true, enableInternString = true;
        if (attr != null)
        {
            if (attr.ConstructorArguments.Length == 1)
            {
                var all = (bool)attr.ConstructorArguments[0].Value!;
                enableIdTracking = enableRefHandling = enableInternString = all;
            }
            else if (attr.ConstructorArguments.Length == 4)
            {
                enableIdTracking = (bool)attr.ConstructorArguments[1].Value!;
                enableRefHandling = (bool)attr.ConstructorArguments[2].Value!;
                enableInternString = (bool)attr.ConstructorArguments[3].Value!;
            }
        }

        // IId tracking: active in OnlyId + All modes
        var isIId = enableIdTracking && type.AllInterfaces.Any(i =>
            i.IsGenericType && i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
        var needsIdScan = isIId;
        // Non-IId ref tracking: active only in All mode
        var needsAllRefScan = !isIId && enableRefHandling;
        var needsInternScan = false;

        // Check properties for string interning or complex children
        foreach (var p in GetAllSerializablePropertySymbols(type))
        {
            // Early exit: if all flags are already true, no need to check more properties
            if (needsIdScan && needsAllRefScan && needsInternScan) break;

            var kind = GetKind(p.Type);

            // String with interning?
            if (enableInternString && kind == PropertyTypeKind.String)
            {
                var internAttr = p.GetAttributes().FirstOrDefault(a =>
                    a.AttributeClass?.ToDisplayString() == "AyCode.Core.Serializers.Binaries.AcStringInternAttribute");
                if (internAttr == null || (internAttr.ConstructorArguments.Length == 1 && (bool)internAttr.ConstructorArguments[0].Value!))
                    needsInternScan = true;
            }

            // Complex child → recurse
            if (kind == PropertyTypeKind.Complex)
            {
                var resolved = p.Type is INamedTypeSymbol nt ? nt.OriginalDefinition : p.Type;
                var childFlags = ComputeNeedsScanCore(resolved, visiting);
                needsIdScan |= childFlags.needsIdScan;
                needsAllRefScan |= childFlags.needsAllRefScan;
                needsInternScan |= childFlags.needsInternScan;
            }

            // Collection → check element type
            if (kind == PropertyTypeKind.Collection)
            {
                var elemType = GetCollectionElementType(p.Type);
                if (elemType != null)
                {
                    var elemKind = GetKind(elemType);
                    if (enableInternString && elemKind == PropertyTypeKind.String)
                        needsInternScan = true;
                    if (elemKind == PropertyTypeKind.Complex)
                    {
                        var resolvedElem = elemType is INamedTypeSymbol ne ? ne.OriginalDefinition : elemType;
                        var elemFlags = ComputeNeedsScanCore(resolvedElem, visiting);
                        needsIdScan |= elemFlags.needsIdScan;
                        needsAllRefScan |= elemFlags.needsAllRefScan;
                        needsInternScan |= elemFlags.needsInternScan;
                    }
                }
            }

            // Dictionary → check key and value types
            if (kind == PropertyTypeKind.Dictionary)
            {
                var (keyType, valueType) = GetDictionaryKeyValueTypes(p.Type);
                if (keyType != null && enableInternString && GetKind(keyType) == PropertyTypeKind.String)
                    needsInternScan = true;
                if (valueType != null)
                {
                    var valKind = GetKind(valueType);
                    if (enableInternString && valKind == PropertyTypeKind.String)
                        needsInternScan = true;
                    if (valKind == PropertyTypeKind.Complex)
                    {
                        var resolvedVal = valueType is INamedTypeSymbol nv ? nv.OriginalDefinition : valueType;
                        var valFlags = ComputeNeedsScanCore(resolvedVal, visiting);
                        needsIdScan |= valFlags.needsIdScan;
                        needsAllRefScan |= valFlags.needsAllRefScan;
                        needsInternScan |= valFlags.needsInternScan;
                    }
                }
            }
        }

        return (needsIdScan, needsAllRefScan, needsInternScan);
    }

    #region FNV-1a Hash (compile-time)

    private static int ComputeFnvHash(string value)
    {
        uint hash = 2166136261;
        for (int i = 0; i < value.Length; i++)
        {
            hash ^= value[i];
            hash *= 16777619;
        }
        return (int)hash;
    }

    /// <summary>
    /// Computes FNV-1a hashes for all serializable properties of a child type.
    /// Property filtering and ordering matches runtime TypeMetadataBase exactly:
    /// derived → base, each level sorted alphabetically, with ignore attribute filtering.
    /// </summary>
    private static int[] ComputeChildPropertyHashes(ITypeSymbol resolvedType)
    {
        // Use hierarchy-walking helper — order matches runtime TypeMetadataBase
        var props = GetAllSerializablePropertySymbols(resolvedType);
        return props.Select(p => ComputeFnvHash(p.Name)).ToArray();
    }

    #endregion

    /// <summary>
    /// Collects all serializable property symbols from the full inheritance hierarchy.
    /// Order matches runtime TypeMetadataBase.GetUnfilteredProperties exactly:
    /// derived → base, each level sorted alphabetically by name.
    /// Filters: public, get+set, non-indexer, non-static, no ignore attributes.
    /// Deduplicates by name (most-derived override wins).
    /// </summary>
    private static List<IPropertySymbol> GetAllSerializablePropertySymbols(ITypeSymbol typeSymbol)
    {
        var result = new List<IPropertySymbol>();
        var seen = new HashSet<string>();

        for (var currentType = typeSymbol as INamedTypeSymbol;
             currentType != null && currentType.SpecialType != SpecialType.System_Object;
             currentType = currentType.BaseType)
        {
            var levelProps = new List<IPropertySymbol>();

            foreach (var member in currentType.GetMembers())
            {
                if (member is IPropertySymbol p &&
                    p.DeclaredAccessibility == Accessibility.Public &&
                    p.GetMethod != null && p.SetMethod != null &&
                    !p.IsIndexer && !p.IsStatic &&
                    seen.Add(p.Name)) // dedup: most-derived wins
                {
                    var hasIgnore = p.GetAttributes().Any(a =>
                    {
                        var name = a.AttributeClass?.Name ?? "";
                        return name == "JsonIgnoreAttribute" || name == "IgnoreMemberAttribute" || name == "BsonIgnoreAttribute";
                    });
                    if (hasIgnore) continue;

                    levelProps.Add(p);
                }
            }

            // Sort each level alphabetically — matches runtime OrderBy(p => p.Name, Ordinal)
            levelProps.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
            result.AddRange(levelProps);
        }

        return result;
    }

    #region Type analysis

    private static bool IsNullableVT(ITypeSymbol t) =>
        t is INamedTypeSymbol n && n.IsGenericType && n.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T;

    private static PropertyTypeKind GetKind(ITypeSymbol type)
    {
        if (type is INamedTypeSymbol n && n.IsGenericType && n.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T)
            return GetKindCore(n.TypeArguments[0], true);
        return GetKindCore(type, false);
    }

    private static PropertyTypeKind GetKindCore(ITypeSymbol type, bool nullable)
    {
        switch (type.SpecialType)
        {
            case SpecialType.System_String: return PropertyTypeKind.String;
            case SpecialType.System_Int32: return nullable ? PropertyTypeKind.NullableInt32 : PropertyTypeKind.Int32;
            case SpecialType.System_Int64: return nullable ? PropertyTypeKind.NullableInt64 : PropertyTypeKind.Int64;
            case SpecialType.System_Int16: return nullable ? PropertyTypeKind.NullableInt16 : PropertyTypeKind.Int16;
            case SpecialType.System_Byte: return nullable ? PropertyTypeKind.NullableByte : PropertyTypeKind.Byte;
            case SpecialType.System_UInt16: return nullable ? PropertyTypeKind.NullableUInt16 : PropertyTypeKind.UInt16;
            case SpecialType.System_UInt32: return nullable ? PropertyTypeKind.NullableUInt32 : PropertyTypeKind.UInt32;
            case SpecialType.System_UInt64: return nullable ? PropertyTypeKind.NullableUInt64 : PropertyTypeKind.UInt64;
            case SpecialType.System_Boolean: return nullable ? PropertyTypeKind.NullableBoolean : PropertyTypeKind.Boolean;
            case SpecialType.System_Single: return nullable ? PropertyTypeKind.NullableSingle : PropertyTypeKind.Single;
            case SpecialType.System_Double: return nullable ? PropertyTypeKind.NullableDouble : PropertyTypeKind.Double;
            case SpecialType.System_Decimal: return nullable ? PropertyTypeKind.NullableDecimal : PropertyTypeKind.Decimal;
            case SpecialType.System_DateTime: return nullable ? PropertyTypeKind.NullableDateTime : PropertyTypeKind.DateTime;
            default: break;
        }
        var fn = type.ToDisplayString();
        if (fn == "System.Guid") return nullable ? PropertyTypeKind.NullableGuid : PropertyTypeKind.Guid;
        if (fn == "System.TimeSpan") return nullable ? PropertyTypeKind.NullableTimeSpan : PropertyTypeKind.TimeSpan;
        if (fn == "System.DateTimeOffset") return nullable ? PropertyTypeKind.NullableDateTimeOffset : PropertyTypeKind.DateTimeOffset;
        if (type.TypeKind == TypeKind.Enum) return nullable ? PropertyTypeKind.NullableEnum : PropertyTypeKind.Enum;
        if (type is IArrayTypeSymbol) return PropertyTypeKind.Collection;
        // Dictionary detection: must come before IEnumerable<T> (Dictionary implements both)
        if (type is INamedTypeSymbol dictNt && dictNt.IsGenericType)
        {
            var orig = dictNt.OriginalDefinition.ToDisplayString();
            if (orig == "System.Collections.Generic.IDictionary<TKey, TValue>" ||
                orig == "System.Collections.Generic.Dictionary<TKey, TValue>" ||
                dictNt.AllInterfaces.Any(ifc => ifc.OriginalDefinition.ToDisplayString() == "System.Collections.Generic.IDictionary<TKey, TValue>"))
                return PropertyTypeKind.Dictionary;
        }
        if (type is INamedTypeSymbol nt && nt.AllInterfaces.Any(iface => iface.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T))
            return PropertyTypeKind.Collection;
        if (type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct) return PropertyTypeKind.Complex;
        return PropertyTypeKind.Unknown;
    }

    /// <summary>
    /// Extracts the element type T from List&lt;T&gt;, T[], IList&lt;T&gt;, IEnumerable&lt;T&gt;.
    /// Returns null if the element type cannot be determined.
    /// </summary>
    private static ITypeSymbol? GetCollectionElementType(ITypeSymbol type)
    {
        // T[] → element type
        if (type is IArrayTypeSymbol arrayType)
            return arrayType.ElementType;

        // Generic collections: List<T>, IList<T>, ICollection<T>, IEnumerable<T>
        if (type is INamedTypeSymbol namedType && namedType.IsGenericType)
        {
            // Direct: List<T>, HashSet<T>, etc. — first type argument
            var iface = namedType.AllInterfaces
                .FirstOrDefault(i => i.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T);
            if (iface != null)
                return iface.TypeArguments[0];
        }

        return null;
    }

    /// <summary>
    /// Extracts key and value types from Dictionary&lt;K,V&gt; or IDictionary&lt;K,V&gt;.
    /// </summary>
    private static (ITypeSymbol? keyType, ITypeSymbol? valueType) GetDictionaryKeyValueTypes(ITypeSymbol type)
    {
        if (type is INamedTypeSymbol nt && nt.IsGenericType)
        {
            var orig = nt.OriginalDefinition.ToDisplayString();
            if (orig == "System.Collections.Generic.Dictionary<TKey, TValue>" ||
                orig == "System.Collections.Generic.IDictionary<TKey, TValue>")
                return (nt.TypeArguments[0], nt.TypeArguments[1]);

            var iface = nt.AllInterfaces.FirstOrDefault(i =>
                i.OriginalDefinition.ToDisplayString() == "System.Collections.Generic.IDictionary<TKey, TValue>");
            if (iface != null)
                return (iface.TypeArguments[0], iface.TypeArguments[1]);
        }
        return (null, null);
    }

    private static bool IsNullableVTKind(PropertyTypeKind k) => k >= PropertyTypeKind.NullableInt32;

    private static PropertyTypeKind Underlying(PropertyTypeKind k) => k switch
    {
        PropertyTypeKind.NullableInt32 => PropertyTypeKind.Int32, PropertyTypeKind.NullableInt64 => PropertyTypeKind.Int64,
        PropertyTypeKind.NullableInt16 => PropertyTypeKind.Int16, PropertyTypeKind.NullableByte => PropertyTypeKind.Byte,
        PropertyTypeKind.NullableUInt16 => PropertyTypeKind.UInt16, PropertyTypeKind.NullableUInt32 => PropertyTypeKind.UInt32,
        PropertyTypeKind.NullableUInt64 => PropertyTypeKind.UInt64, PropertyTypeKind.NullableBoolean => PropertyTypeKind.Boolean,
        PropertyTypeKind.NullableSingle => PropertyTypeKind.Single, PropertyTypeKind.NullableDouble => PropertyTypeKind.Double,
        PropertyTypeKind.NullableDecimal => PropertyTypeKind.Decimal, PropertyTypeKind.NullableDateTime => PropertyTypeKind.DateTime,
        PropertyTypeKind.NullableDateTimeOffset => PropertyTypeKind.DateTimeOffset, PropertyTypeKind.NullableTimeSpan => PropertyTypeKind.TimeSpan,
        PropertyTypeKind.NullableGuid => PropertyTypeKind.Guid, PropertyTypeKind.NullableEnum => PropertyTypeKind.Enum,
        _ => PropertyTypeKind.Unknown
    };

    #endregion
}
