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

namespace AyCode.Core.Serializers.SourceGenerator;

/// <summary>
/// Class-info extraction pass — transforms a Roslyn <see cref="GeneratorAttributeSyntaxContext"/>
/// (a class/struct annotated with <c>[AcBinarySerializable]</c>) into the <see cref="SerializableClassInfo"/>
/// model consumed by the emit passes (writer / reader / scan / init).
///
/// <para>Reads the attribute's feature flags (1-, 4-, 5-, 6-bool ctor variants), walks the inheritance
/// hierarchy via <c>GetAllSerializablePropertySymbols</c>, and computes per-property metadata: kind,
/// nullability, intern eligibility, complex / collection / dictionary element types, generated-writer
/// pointers, FNV hashes for inline-metadata, and recursive scan-need flags.</para>
/// </summary>
public partial class AcBinarySourceGenerator
{
    private static SerializableClassInfo? GetClassInfo(GeneratorAttributeSyntaxContext context)
    {
        if (!(context.TargetSymbol is INamedTypeSymbol typeSymbol))
            return null;

        var namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
            ? string.Empty
            : typeSymbol.ContainingNamespace.ToDisplayString();

        var properties = new List<PropInfo>();

        // Read feature flags from [AcBinarySerializable] — disabled features eliminate
        // corresponding code blocks from generated ScanObject/WriteProperties.
        var enableIdTracking = true;
        var enableRefHandling = true;
        var enableInternString = true;
        var enableMetadata = true;
        var enablePropertyFilter = true;
        var enablePolymorphDetect = true;
        var binarySerializableAttr = typeSymbol.GetAttributes().FirstOrDefault(a =>
            a.AttributeClass?.ToDisplayString() == AttributeName);
        if (binarySerializableAttr != null)
        {
            if (binarySerializableAttr.ConstructorArguments.Length == 1)
            {
                // Single bool ctor: AcBinarySerializable(enableAllFeatures)
                var all = (bool)binarySerializableAttr.ConstructorArguments[0].Value!;
                enableIdTracking = all;
                enableRefHandling = all;
                enableInternString = all;
                enableMetadata = all;
                enablePropertyFilter = all;
                enablePolymorphDetect = all;
            }
            else if (binarySerializableAttr.ConstructorArguments.Length == 4)
            {
                // Four bool ctor: (metadata, idTracking, refHandling, internString) — filter + polymorph default to true
                enableMetadata = (bool)binarySerializableAttr.ConstructorArguments[0].Value!;
                enableIdTracking = (bool)binarySerializableAttr.ConstructorArguments[1].Value!;
                enableRefHandling = (bool)binarySerializableAttr.ConstructorArguments[2].Value!;
                enableInternString = (bool)binarySerializableAttr.ConstructorArguments[3].Value!;
            }
            else if (binarySerializableAttr.ConstructorArguments.Length == 5)
            {
                // Five bool ctor: (metadata, idTracking, refHandling, internString, propertyFilter) — polymorph defaults to true
                enableMetadata = (bool)binarySerializableAttr.ConstructorArguments[0].Value!;
                enableIdTracking = (bool)binarySerializableAttr.ConstructorArguments[1].Value!;
                enableRefHandling = (bool)binarySerializableAttr.ConstructorArguments[2].Value!;
                enableInternString = (bool)binarySerializableAttr.ConstructorArguments[3].Value!;
                enablePropertyFilter = (bool)binarySerializableAttr.ConstructorArguments[4].Value!;
            }
            else if (binarySerializableAttr.ConstructorArguments.Length == 6)
            {
                // Six bool ctor: (metadata, idTracking, refHandling, internString, propertyFilter, polymorphDetect)
                enableMetadata = (bool)binarySerializableAttr.ConstructorArguments[0].Value!;
                enableIdTracking = (bool)binarySerializableAttr.ConstructorArguments[1].Value!;
                enableRefHandling = (bool)binarySerializableAttr.ConstructorArguments[2].Value!;
                enableInternString = (bool)binarySerializableAttr.ConstructorArguments[3].Value!;
                enablePropertyFilter = (bool)binarySerializableAttr.ConstructorArguments[4].Value!;
                enablePolymorphDetect = (bool)binarySerializableAttr.ConstructorArguments[5].Value!;
            }
        }

        foreach (var p in GetAllSerializablePropertySymbols(typeSymbol))
        {
            // String interning attribútum detektálás (null = no attr, true/false = explicit)
                bool? stringInternAttr = null;
                if (!enableInternString)
                {
                    stringInternAttr = false;
                }
                else if (GetKind(p.Type) == PropertyTypeKind.String)
                {
                    var attr = p.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "AyCode.Core.Serializers.Binaries.AcStringInternAttribute");
                    if (attr != null && attr.ConstructorArguments.Length == 1 && attr.ConstructorArguments[0].Kind == TypedConstantKind.Primitive)
                    {
                        stringInternAttr = (bool)attr.ConstructorArguments[0].Value!;
                    }
                }

                // For typeof(): strip trailing '?' from nullable reference types (typeof(T?) is invalid for ref types)
                // Nullable value types (int?, Guid?) keep '?' because typeof(int?) == typeof(Nullable<int>) is valid
                var typeDisplayName = p.Type.ToDisplayString();
                var typeNameForTypeof = (p.Type.NullableAnnotation == NullableAnnotation.Annotated && !p.Type.IsValueType)
                    ? typeDisplayName.TrimEnd('?')
                    : typeDisplayName;

                // Direct object write detection for Complex property types:
                // Check if the property type has [AcBinarySerializable] (→ has generated writer)
                // and if it implements IId<T> (→ needs ref tracking in generated code)
                var kind = GetKind(p.Type);
                bool hasGenWriter = false;
                bool propTypeIsIId = false;
                bool propEnableMetadata = true;
                bool childNeedsIdScan = true;
                bool childNeedsAllRefScan = true;
                bool childNeedsInternScan = true;
                string? writerClassName = null;
                string? propIdTypeName = null;
                int childTypeNameHash = 0;
                int[]? childPropertyHashes = null;
                if (kind == PropertyTypeKind.Complex)
                {
                    // Resolve to the actual type symbol (strip nullable annotation for ref types)
                    // For SharedTag? → SharedTag. OriginalDefinition handles generic types.
                    var resolvedType = p.Type is INamedTypeSymbol namedPropType
                        ? namedPropType.OriginalDefinition
                        : p.Type;

                    hasGenWriter = resolvedType.Locations.Any(l => l.IsInSource)
                        && resolvedType.GetAttributes().Any(a =>
                            a.AttributeClass?.ToDisplayString() == AttributeName);

                    if (hasGenWriter)
                    {
                        // Read child type's EnableMetadataFeature
                        propEnableMetadata = ReadEnableMetadata(resolvedType);
                        var childScanFlags = ComputeNeedsScan(resolvedType);
                        childNeedsIdScan = childScanFlags.needsIdScan;
                        childNeedsAllRefScan = childScanFlags.needsAllRefScan;
                        childNeedsInternScan = childScanFlags.needsInternScan;
                        var iidIface = resolvedType.AllInterfaces.FirstOrDefault(i =>
                            i.IsGenericType &&
                            i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
                        propTypeIsIId = iidIface != null;
                        if (iidIface != null)
                            propIdTypeName = iidIface.TypeArguments[0].ToDisplayString();

                        // Writer class: {Namespace}.{FlatName}_GeneratedWriter
                        var flatName = BuildFlatName((INamedTypeSymbol)resolvedType);
                        var ns = resolvedType.ContainingNamespace.IsGlobalNamespace
                            ? string.Empty
                            : resolvedType.ContainingNamespace.ToDisplayString();
                        writerClassName = string.IsNullOrEmpty(ns)
                            ? $"{flatName}_GeneratedWriter"
                            : $"{ns}.{flatName}_GeneratedWriter";

                        // UseMetadata: compute child type hash-es for inline metadata
                        childTypeNameHash = ComputeFnvHash(resolvedType.Name);
                        childPropertyHashes = ComputeChildPropertyHashes(resolvedType);
                    }
                }

                // Collection element type analysis for inline collection write
                PropertyTypeKind elemKind = PropertyTypeKind.Unknown;
                bool elemHasGenWriter = false;
                bool elemIsIId = false;
                bool elemEnableMetadata = true;
                bool elemNeedsIdScan = true;
                bool elemNeedsAllRefScan = true;
                bool elemNeedsInternScan = true;
                string? elemWriterClassName = null;
                string? elemIdTypeName = null;
                string? collKind = null;
                string? collAddMethod = null;
                bool collHasCapacityCtor = false;
                string? elemFullTypeName = null;
                int elementTypeNameHash = 0;
                int[]? elementPropertyHashes = null;
                if (kind == PropertyTypeKind.Collection)
                {
                    var elemType = GetCollectionElementType(p.Type);
                    if (elemType != null)
                    {
                        elemKind = GetKind(elemType);
                        elemFullTypeName = elemType.ToDisplayString();

                        // Detect collection shape for inline write
                        if (p.Type is IArrayTypeSymbol)
                            collKind = "Array";
                        else if (p.Type is INamedTypeSymbol collNamedType)
                        {
                            var origDef = collNamedType.OriginalDefinition.ToDisplayString();
                            collKind = origDef switch
                            {
                                "System.Collections.Generic.List<T>" => "List",
                                "System.Collections.Generic.IList<T>" => "IndexedCollection",
                                "System.Collections.Generic.IReadOnlyList<T>" => "IndexedCollection",
                                "System.Collections.Generic.HashSet<T>" => "Counted",       // has Count, no indexer
                                "System.Collections.Generic.Queue<T>" => "Counted",
                                "System.Collections.Generic.ICollection<T>" => "Counted",
                                "System.Collections.Generic.IReadOnlyCollection<T>" => "Counted",
                                "System.Collections.Generic.SortedSet<T>" => "Counted",
                                "System.Collections.Generic.LinkedList<T>" => "Counted",
                                _ => null
                            };

                            // Determine add method + capacity ctor for Counted concrete types
                            if (collKind == "Counted")
                            {
                                collAddMethod = origDef switch
                                {
                                    "System.Collections.Generic.HashSet<T>" => "Add",
                                    "System.Collections.Generic.SortedSet<T>" => "Add",
                                    "System.Collections.Generic.Queue<T>" => "Enqueue",
                                    "System.Collections.Generic.LinkedList<T>" => "AddLast",
                                    _ => null // ICollection<T>, IReadOnlyCollection<T> → backed by List<T>
                                };
                                collHasCapacityCtor = origDef is
                                    "System.Collections.Generic.HashSet<T>" or
                                    "System.Collections.Generic.Queue<T>";
                            }
                        }

                        // For Complex element types, check for generated writer
                        if (elemKind == PropertyTypeKind.Complex)
                        {
                            var resolvedElem = elemType is INamedTypeSymbol namedElem
                                ? namedElem.OriginalDefinition : elemType;
                            elemHasGenWriter = resolvedElem.Locations.Any(l => l.IsInSource)
                                && resolvedElem.GetAttributes().Any(a =>
                                    a.AttributeClass?.ToDisplayString() == AttributeName);
                            if (elemHasGenWriter)
                            {
                                // Read element type's EnableMetadataFeature
                                elemEnableMetadata = ReadEnableMetadata(resolvedElem);
                                var elemScanFlags = ComputeNeedsScan(resolvedElem);
                                elemNeedsIdScan = elemScanFlags.needsIdScan;
                                elemNeedsAllRefScan = elemScanFlags.needsAllRefScan;
                                elemNeedsInternScan = elemScanFlags.needsInternScan;
                                var elemIidIface = resolvedElem.AllInterfaces.FirstOrDefault(ifc =>
                                    ifc.IsGenericType &&
                                    ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
                                elemIsIId = elemIidIface != null;
                                if (elemIidIface != null)
                                    elemIdTypeName = elemIidIface.TypeArguments[0].ToDisplayString();

                                var elemFlatName = BuildFlatName((INamedTypeSymbol)resolvedElem);
                                var ens = resolvedElem.ContainingNamespace.IsGlobalNamespace
                                    ? string.Empty : resolvedElem.ContainingNamespace.ToDisplayString();
                                elemWriterClassName = string.IsNullOrEmpty(ens)
                                    ? $"{elemFlatName}_GeneratedWriter"
                                    : $"{ens}.{elemFlatName}_GeneratedWriter";

                                // UseMetadata: compute element type hash-es for inline metadata
                                elementTypeNameHash = ComputeFnvHash(resolvedElem.Name);
                                elementPropertyHashes = ComputeChildPropertyHashes(resolvedElem);
                            }
                        }
                    }
                }

                // Dictionary key/value type analysis for inline dictionary read
                PropertyTypeKind dictKeyKind = PropertyTypeKind.Unknown;
                PropertyTypeKind dictValueKind = PropertyTypeKind.Unknown;
                string? dictKeyTypeName = null;
                string? dictValueTypeName = null;
                bool dictValueHasGenWriter = false;
                string? dictValueWriterClassName = null;
                bool dictValueIsIId = false;
                bool dictValueEnableMetadata = true;
                bool dictValueNeedsIdScan = true;
                bool dictValueNeedsAllRefScan = true;
                bool dictValueNeedsInternScan = true;
                int dictValueTypeNameHash = 0;
                int[]? dictValuePropertyHashes = null;
                if (kind == PropertyTypeKind.Dictionary)
                {
                    var (keyType, valueType) = GetDictionaryKeyValueTypes(p.Type);
                    if (keyType != null)
                    {
                        dictKeyKind = GetKind(keyType);
                        dictKeyTypeName = keyType.ToDisplayString();
                    }
                    if (valueType != null)
                    {
                        dictValueKind = GetKind(valueType);
                        dictValueTypeName = valueType.ToDisplayString();
                        if (dictValueKind == PropertyTypeKind.Complex)
                        {
                            var resolvedValue = valueType is INamedTypeSymbol nvt ? nvt.OriginalDefinition : valueType;
                            dictValueHasGenWriter = resolvedValue.Locations.Any(l => l.IsInSource)
                                && resolvedValue.GetAttributes().Any(a =>
                                    a.AttributeClass?.ToDisplayString() == AttributeName);
                            if (dictValueHasGenWriter)
                            {
                                var vfn = BuildFlatName((INamedTypeSymbol)resolvedValue);
                                var vns = resolvedValue.ContainingNamespace.IsGlobalNamespace
                                    ? string.Empty : resolvedValue.ContainingNamespace.ToDisplayString();
                                dictValueWriterClassName = string.IsNullOrEmpty(vns)
                                    ? $"{vfn}_GeneratedWriter"
                                    : $"{vns}.{vfn}_GeneratedWriter";

                                dictValueEnableMetadata = ReadEnableMetadata(resolvedValue);
                                var dvScanFlags = ComputeNeedsScan(resolvedValue);
                                dictValueNeedsIdScan = dvScanFlags.needsIdScan;
                                dictValueNeedsAllRefScan = dvScanFlags.needsAllRefScan;
                                dictValueNeedsInternScan = dvScanFlags.needsInternScan;
                                var dvIidIface = resolvedValue.AllInterfaces.FirstOrDefault(ifc =>
                                    ifc.IsGenericType &&
                                    ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
                                dictValueIsIId = dvIidIface != null;
                                dictValueTypeNameHash = ComputeFnvHash(resolvedValue.Name);
                                dictValuePropertyHashes = ComputeChildPropertyHashes(resolvedValue);
                            }
                        }
                    }
                }

                properties.Add(new PropInfo(
                    p.Name,
                    typeDisplayName,
                    typeNameForTypeof,
                    kind,
                    p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableVT(p.Type),
                    p.Type.SpecialType == SpecialType.System_Object,
                    stringInternAttr, hasGenWriter, propTypeIsIId, writerClassName, propIdTypeName,
                    elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, elemIdTypeName, collKind, elemFullTypeName,
                    collAddMethod, collHasCapacityCtor,
                    dictKeyKind, dictValueKind, dictKeyTypeName, dictValueTypeName, dictValueHasGenWriter, dictValueWriterClassName,
                    dictValueIsIId, dictValueEnableMetadata, dictValueTypeNameHash, dictValuePropertyHashes,
                    dictValueNeedsIdScan, dictValueNeedsAllRefScan, dictValueNeedsInternScan,
                    childTypeNameHash, childPropertyHashes,
                    elementTypeNameHash, elementPropertyHashes,
                    propEnableMetadata, elemEnableMetadata,
                    childNeedsIdScan, childNeedsAllRefScan, childNeedsInternScan,
                    elemNeedsIdScan, elemNeedsAllRefScan, elemNeedsInternScan));
        }

        // IId<T>: Id first (index 0), then alphabetical — matches runtime TypeMetadataBase ordering
        // If EnableIdTrackingFeature == false, skip IId detection entirely → isIId = false
        var isIId = false;
        string? idTypeName = null;
        if (enableIdTracking)
        {
            var iidInterface = typeSymbol.AllInterfaces.FirstOrDefault(i =>
                i.IsGenericType &&
                i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
            if (iidInterface != null)
            {
                isIId = true;
                idTypeName = iidInterface.TypeArguments[0].ToDisplayString();
            }
        }

        // Properties are already in runtime-matching order from GetAllSerializablePropertySymbols:
        // derived → base, each level sorted alphabetically (matches TypeMetadataBase.GetUnfilteredProperties).

        var className = BuildFlatName(typeSymbol);
        var typeNameHash = ComputeFnvHash(typeSymbol.Name);
        var propertyNameHashes = properties.Select(prop => ComputeFnvHash(prop.Name)).ToArray();
        var selfScanFlags = ComputeNeedsScan(typeSymbol);
        return new SerializableClassInfo(namespaceName, className, typeSymbol.ToDisplayString(), properties, isIId, idTypeName, enableRefHandling, typeNameHash, propertyNameHashes, enableMetadata, enablePropertyFilter, enablePolymorphDetect, enableInternString, selfScanFlags.needsIdScan, selfScanFlags.needsAllRefScan, selfScanFlags.needsInternScan);
    }
}
