using System.Collections.Generic;

namespace AyCode.Core.Serializers.SourceGenerator;

// Source-generator model types — pure POCO data carriers describing a `[AcBinarySerializable]` type
// and its serializable properties. Consumed by all emit / diagnostics / analysis passes in the partial
// `AcBinarySourceGenerator` class (see siblings `*.GenWriter.cs`, `*.GenReader.cs`, etc.).

internal sealed class SerializableClassInfo
{
    public string Namespace { get; }
    public string ClassName { get; }
    public string FullTypeName { get; }
    public List<PropInfo> Properties { get; }
    /// <summary>True if this type implements IId&lt;T&gt;</summary>
    public bool IsIId { get; }
    /// <summary>The Id type name ("int", "long", "System.Guid") if IsIId, null otherwise</summary>
    public string? IdTypeName { get; }
    /// <summary>True if EnableRefHandlingFeature is enabled — controls non-IId All mode tracking code emission.</summary>
    public bool EnableRefHandling { get; }
    /// <summary>FNV-1a hash of ClassName (matches runtime SourceType.Name hash)</summary>
    public int TypeNameHash { get; }
    /// <summary>FNV-1a hash of each property name, in property order</summary>
    public int[] PropertyNameHashes { get; }
    /// <summary>When false, skip inline metadata and use markerless property write for this type.</summary>
    public bool EnableMetadata { get; }
    /// <summary>True if EnablePropertyFilterFeature is enabled — controls per-property HasPropertyFilter
    /// guard emission in WriteProperties / ScanObject. When false, the filter check is omitted entirely
    /// → leaner generated code on the hot path (typical for high-throughput types that never use a filter).</summary>
    public bool EnablePropertyFilter { get; }
    /// <summary>True if EnablePolymorphDetectFeature is enabled — controls <c>ObjectWithTypeName</c> + AQN
    /// prefix emit on <c>System.Object</c>-declared properties. When false, the prefix is suppressed
    /// AND ACBIN002 fires at build time if such a property exists on this type (guarding against silent
    /// wire corruption). Opt-out is intentional: dev guarantees no polymorphic <c>object</c> property
    /// will be serialized on this type, or all such properties are excluded via <c>[AcBinaryIgnore]</c>.</summary>
    public bool EnablePolymorphDetect { get; }
    /// <summary>True if EnableInternStringFeature is enabled — controls whether the SGen-emitted reader
    /// contains <c>StringInterned</c>, <c>StringInternFirstSmall</c>, <c>StringInternFirstMedium</c> case-ágakat.
    /// When false, those cases are omitted (the writer doesn't emit those markers when intern is off,
    /// so the reader doesn't need to handle them). Leaner switch dispatch (~30% fewer string cases) +
    /// smaller IL → faster cold-start JIT + smaller AOT publish.</summary>
    public bool EnableInternString { get; }
    /// <summary>When true, type subtree has IId types needing scan (active in OnlyId + All).</summary>
    public bool NeedsIdScan { get; }
    /// <summary>When true, type subtree has non-IId ref tracking (active only in All mode).</summary>
    public bool NeedsAllRefScan { get; }
    /// <summary>When true, type subtree needs string interning scan.</summary>
    public bool NeedsInternScan { get; }
    /// <summary>Derived: NeedsIdScan || NeedsAllRefScan.</summary>
    public bool NeedsRefScan => NeedsIdScan || NeedsAllRefScan;
    /// <summary>Derived: any scan axis active.</summary>
    public bool NeedsScan => NeedsIdScan || NeedsAllRefScan || NeedsInternScan;
    public SerializableClassInfo(string ns, string cn, string ftn, List<PropInfo> p, bool isIId, string? idTypeName, bool enableRefHandling, int typeNameHash, int[] propertyNameHashes, bool enableMetadata, bool enablePropertyFilter, bool enablePolymorphDetect, bool enableInternString, bool needsIdScan, bool needsAllRefScan, bool needsInternScan)
    { Namespace = ns; ClassName = cn; FullTypeName = ftn; Properties = p; IsIId = isIId; IdTypeName = idTypeName; EnableRefHandling = enableRefHandling; TypeNameHash = typeNameHash; PropertyNameHashes = propertyNameHashes; EnableMetadata = enableMetadata; EnablePropertyFilter = enablePropertyFilter; EnablePolymorphDetect = enablePolymorphDetect; EnableInternString = enableInternString; NeedsIdScan = needsIdScan; NeedsAllRefScan = needsAllRefScan; NeedsInternScan = needsInternScan; }
}

internal sealed class PropInfo
{
    public string Name { get; }
    public string TypeName { get; }
    /// <summary>
    /// Type name safe for typeof() — nullable ref type annotation stripped (typeof(T?) invalid for ref types).
    /// </summary>
    public string TypeNameForTypeof { get; }
    public PropertyTypeKind TypeKind { get; }
    public bool IsNullable { get; }
    /// <summary>
    /// Pre-computed interning flags matching runtime BinaryPropertyAccessorBase._interningFlags.
    /// Bit layout: bit N = eligible when StringInterningMode == N.
    /// None=0 → bit 0 never set. Attribute=1 → bit 1. All=2 → bit 2.
    /// No attr: 0b100 (4), [AcStringIntern(true)]: 0b110 (6), [AcStringIntern(false)]: 0b000 (0).
    /// </summary>
    public int InterningFlags { get; }

    /// <summary>True when declared property type is System.Object. Runtime type dispatch needed.</summary>
    public bool IsObjectDeclaredType { get; }
    /// <summary>True if the Complex property type has [AcBinarySerializable] → has a generated writer.</summary>
    public bool HasGeneratedWriter { get; }
    /// <summary>True if the Complex property type implements IId&lt;T&gt; → needs ref tracking in write pass.</summary>
    public bool IsIId { get; }
    /// <summary>Generated writer class name, e.g. "SharedTag_GeneratedWriter". Only set when HasGeneratedWriter.</summary>
    public string? WriterClassName { get; }
    /// <summary>Id type name ("int", "long", "System.Guid") for IId child types. Null if not IId.</summary>
    public string? IdTypeName { get; }

    // Collection element metadata — set when TypeKind == Collection and element type is Complex with generated writer
    /// <summary>Element type kind for collection properties. Only meaningful when TypeKind == Collection.</summary>
    public PropertyTypeKind ElementKind { get; }
    /// <summary>True if collection element type has [AcBinarySerializable].</summary>
    public bool ElementHasGeneratedWriter { get; }
    /// <summary>True if collection element type implements IId&lt;T&gt;.</summary>
    public bool ElementIsIId { get; }
    /// <summary>Generated writer class name for collection element type.</summary>
    public string? ElementWriterClassName { get; }
    /// <summary>Id type name for collection element IId types. Null if not IId.</summary>
    public string? ElementIdTypeName { get; }
    /// <summary>Collection type: "List", "Array", "IndexedCollection", "Counted", or null (unknown — fallback to runtime).</summary>
    public string? CollectionKind { get; }
    /// <summary>Full element type name for generated code (e.g. "SharedTag").</summary>
    public string? ElementFullTypeName { get; }
    /// <summary>Add method for Counted concrete collections. null → List&lt;T&gt;.Add(), "Add" → HashSet/SortedSet, "Enqueue" → Queue, "AddLast" → LinkedList.</summary>
    public string? CollectionAddMethod { get; }
    /// <summary>True if the concrete Counted collection has a capacity constructor (HashSet, Queue).</summary>
    public bool CollectionHasCapacityCtor { get; }

    // Dictionary metadata — set when TypeKind == Dictionary
    /// <summary>Key type kind for dictionary properties.</summary>
    public PropertyTypeKind DictKeyKind { get; }
    /// <summary>Value type kind for dictionary properties.</summary>
    public PropertyTypeKind DictValueKind { get; }
    /// <summary>Key type name for generated code.</summary>
    public string? DictKeyTypeName { get; }
    /// <summary>Value type name for generated code.</summary>
    public string? DictValueTypeName { get; }
    /// <summary>True if dictionary value type has [AcBinarySerializable].</summary>
    public bool DictValueHasGeneratedWriter { get; }
    /// <summary>Generated writer class name for dictionary value type.</summary>
    public string? DictValueWriterClassName { get; }
    /// <summary>True if dictionary value type implements IId&lt;T&gt;.</summary>
    public bool DictValueIsIId { get; }
    /// <summary>When false, dict value type skips inline metadata.</summary>
    public bool DictValueEnableMetadata { get; }
    /// <summary>FNV-1a hash of dict value type name.</summary>
    public int DictValueTypeNameHash { get; }
    /// <summary>FNV-1a hashes of dict value type's properties.</summary>
    public int[]? DictValuePropertyHashes { get; }
    /// <summary>When true, dict value subtree has IId types needing scan.</summary>
    public bool DictValueNeedsIdScan { get; }
    /// <summary>When true, dict value subtree has non-IId ref tracking.</summary>
    public bool DictValueNeedsAllRefScan { get; }
    /// <summary>When true, dict value subtree needs string interning scan.</summary>
    public bool DictValueNeedsInternScan { get; }
    /// <summary>Derived: DictValueNeedsIdScan || DictValueNeedsAllRefScan.</summary>
    public bool DictValueNeedsRefScan => DictValueNeedsIdScan || DictValueNeedsAllRefScan;
    /// <summary>Derived: any dict value scan axis active.</summary>
    public bool DictValueNeedsScan => DictValueNeedsIdScan || DictValueNeedsAllRefScan || DictValueNeedsInternScan;

    // UseMetadata inline hash-ek (Complex/Collection child típushoz)
    /// <summary>FNV-1a hash of child type name (Complex property). Only set when HasGeneratedWriter.</summary>
    public int ChildTypeNameHash { get; }
    /// <summary>FNV-1a hashes of child type's properties. Only set when HasGeneratedWriter.</summary>
    public int[]? ChildPropertyHashes { get; }
    /// <summary>FNV-1a hash of collection element type name. Only set when ElementHasGeneratedWriter.</summary>
    public int ElementTypeNameHash { get; }
    /// <summary>FNV-1a hashes of collection element type's properties. Only set when ElementHasGeneratedWriter.</summary>
    public int[]? ElementPropertyHashes { get; }
    /// <summary>When false, child Complex type skips inline metadata in generated code.</summary>
    public bool ChildEnableMetadata { get; }
    /// <summary>When false, collection element type skips inline metadata in generated code.</summary>
    public bool ElementEnableMetadata { get; }
    /// <summary>When true, child subtree has IId types needing scan (active in OnlyId + All).</summary>
    public bool ChildNeedsIdScan { get; }
    /// <summary>When true, child subtree has non-IId ref tracking (active only in All mode).</summary>
    public bool ChildNeedsAllRefScan { get; }
    /// <summary>When true, child subtree needs string interning scan.</summary>
    public bool ChildNeedsInternScan { get; }
    /// <summary>Derived: ChildNeedsIdScan || ChildNeedsAllRefScan.</summary>
    public bool ChildNeedsRefScan => ChildNeedsIdScan || ChildNeedsAllRefScan;
    /// <summary>Derived: any child scan axis active.</summary>
    public bool ChildNeedsScan => ChildNeedsIdScan || ChildNeedsAllRefScan || ChildNeedsInternScan;
    /// <summary>When true, element subtree has IId types needing scan (active in OnlyId + All).</summary>
    public bool ElementNeedsIdScan { get; }
    /// <summary>When true, element subtree has non-IId ref tracking (active only in All mode).</summary>
    public bool ElementNeedsAllRefScan { get; }
    /// <summary>When true, element subtree needs string interning scan.</summary>
    public bool ElementNeedsInternScan { get; }
    /// <summary>Derived: ElementNeedsIdScan || ElementNeedsAllRefScan.</summary>
    public bool ElementNeedsRefScan => ElementNeedsIdScan || ElementNeedsAllRefScan;
    /// <summary>Derived: any element scan axis active.</summary>
    public bool ElementNeedsScan => ElementNeedsIdScan || ElementNeedsAllRefScan || ElementNeedsInternScan;

    public PropInfo(string n, string tn, string tnForTypeof, PropertyTypeKind tk, bool nullable,
        bool isObjectDeclaredType = false,
        bool? stringInternAttr = null, bool hasGeneratedWriter = false, bool isIId = false, string? writerClassName = null, string? idTypeName = null,
        PropertyTypeKind elementKind = PropertyTypeKind.Unknown, bool elementHasGenWriter = false, bool elementIsIId = false,
        string? elementWriterClassName = null, string? elementIdTypeName = null, string? collectionKind = null, string? elementFullTypeName = null,
        string? collectionAddMethod = null, bool collectionHasCapacityCtor = false,
        PropertyTypeKind dictKeyKind = PropertyTypeKind.Unknown, PropertyTypeKind dictValueKind = PropertyTypeKind.Unknown,
        string? dictKeyTypeName = null, string? dictValueTypeName = null,
        bool dictValueHasGeneratedWriter = false, string? dictValueWriterClassName = null,
        bool dictValueIsIId = false, bool dictValueEnableMetadata = true,
        int dictValueTypeNameHash = 0, int[]? dictValuePropertyHashes = null,
        bool dictValueNeedsIdScan = true, bool dictValueNeedsAllRefScan = true, bool dictValueNeedsInternScan = true,
        int childTypeNameHash = 0, int[]? childPropertyHashes = null,
        int elementTypeNameHash = 0, int[]? elementPropertyHashes = null,
        bool childEnableMetadata = true, bool elementEnableMetadata = true,
        bool childNeedsIdScan = true, bool childNeedsAllRefScan = true, bool childNeedsInternScan = true,
        bool elementNeedsIdScan = true, bool elementNeedsAllRefScan = true, bool elementNeedsInternScan = true)
    {
        Name = n;
        TypeName = tn;
        TypeNameForTypeof = tnForTypeof;
        TypeKind = tk;
        IsNullable = nullable;
        IsObjectDeclaredType = isObjectDeclaredType;
        HasGeneratedWriter = hasGeneratedWriter;
        IsIId = isIId;
        WriterClassName = writerClassName;
        IdTypeName = idTypeName;
        ElementKind = elementKind;
        ElementHasGeneratedWriter = elementHasGenWriter;
        ElementIsIId = elementIsIId;
        ElementWriterClassName = elementWriterClassName;
        ElementIdTypeName = elementIdTypeName;
        CollectionKind = collectionKind;
        ElementFullTypeName = elementFullTypeName;
        CollectionAddMethod = collectionAddMethod;
        CollectionHasCapacityCtor = collectionHasCapacityCtor;
        DictKeyKind = dictKeyKind;
        DictValueKind = dictValueKind;
        DictKeyTypeName = dictKeyTypeName;
        DictValueTypeName = dictValueTypeName;
        DictValueHasGeneratedWriter = dictValueHasGeneratedWriter;
        DictValueWriterClassName = dictValueWriterClassName;
        DictValueIsIId = dictValueIsIId;
        DictValueEnableMetadata = dictValueEnableMetadata;
        DictValueTypeNameHash = dictValueTypeNameHash;
        DictValuePropertyHashes = dictValuePropertyHashes;
        DictValueNeedsIdScan = dictValueNeedsIdScan;
        DictValueNeedsAllRefScan = dictValueNeedsAllRefScan;
        DictValueNeedsInternScan = dictValueNeedsInternScan;
        ChildTypeNameHash = childTypeNameHash;
        ChildPropertyHashes = childPropertyHashes;
        ElementTypeNameHash = elementTypeNameHash;
        ElementPropertyHashes = elementPropertyHashes;
        ChildEnableMetadata = childEnableMetadata;
        ElementEnableMetadata = elementEnableMetadata;
        ChildNeedsIdScan = childNeedsIdScan;
        ChildNeedsAllRefScan = childNeedsAllRefScan;
        ChildNeedsInternScan = childNeedsInternScan;
        ElementNeedsIdScan = elementNeedsIdScan;
        ElementNeedsAllRefScan = elementNeedsAllRefScan;
        ElementNeedsInternScan = elementNeedsInternScan;
        // Mirror runtime _interningFlags computation from BinaryPropertyAccessorBase
        int flags = 0;
        if (stringInternAttr == true)  flags |= (1 << 1); // Attribute bit
        if (stringInternAttr != false) flags |= (1 << 2); // All bit
        InterningFlags = flags;
    }
}

internal enum PropertyTypeKind
{
    Unknown, String, Int32, Int64, Int16, Byte, UInt16, UInt32, UInt64,
    Boolean, Single, Double, Decimal, DateTime, DateTimeOffset, TimeSpan, Guid, Enum,
    Collection, Complex, Dictionary,
    NullableInt32, NullableInt64, NullableInt16, NullableByte, NullableUInt16, NullableUInt32, NullableUInt64,
    NullableBoolean, NullableSingle, NullableDouble, NullableDecimal, NullableDateTime,
    NullableDateTimeOffset, NullableTimeSpan, NullableGuid, NullableEnum
}

/// <summary>
/// Single source of truth for the compile-time decision: does the SGen-emit need a full ref-aware
/// switch (<c>Object</c> / <c>ObjectRefFirst</c> / <c>Null</c> / <c>ObjectRef</c> / FixObj) for a
/// given Complex property or collection element, OR can it use the zero-branch path
/// (<c>Object</c> / <c>Null</c> / FixObj only)?
///
/// <para><b>Predicate semantics</b>: the decision depends EXCLUSIVELY on whether the child
/// element subtree may emit ref markers — captured by <c>PropInfo.ChildNeedsRefScan</c> /
/// <c>PropInfo.ElementNeedsRefScan</c>. The parent-level <c>EnableRefHandlingFeature</c> flag is
/// <b>NOT</b> a factor here — that flag governs only the parent's SELF-tracking emit in the scan
/// pass (<c>GenWriter.cs</c> line 140), it does NOT suppress marker dispatch for child element
/// properties of THIS type.</para>
///
/// <para><b>Writer / reader symmetry</b> — invoked from BOTH sides so the compile-time decision is
/// identical at every call site:</para>
/// <list type="bullet">
///   <item><c>GenReader.EmitReadComplex</c> — guards zero-branch vs full ref-aware switch.</item>
///   <item><c>GenReader.EmitReadCollectionElement</c> — same guard for collection-element dispatch.</item>
///   <item><c>GenReader.EmitReadDictionary</c> — same guard for dictionary-value dispatch.</item>
///   <item><c>GenWriter.EmitDirectCollectionWrite</c> — guards <c>Object</c>-only vs
///   <c>WriteObjectRefMarker*</c> (runtime decide) emit on the writer side.</item>
/// </list>
///
/// <para><b>Why a generator-only helper, not a runtime helper</b> — the result is inlined into
/// the generated code as either the zero-branch ag or the full-switch ag. The predicate runs
/// once per emit-site at generation time; the runtime code has zero overhead from this abstraction
/// (no method call, no branch on the runtime hot path).</para>
///
/// <para>Regression target: <c>AcBinarySerializerIIdReferenceTests.Serialize_RefMarkerCollectionElement_ParentRefHandlingFeatureOff_DriftReproduction</c>.</para>
/// </summary>
internal static class RefAwareEmitPredicate
{
    /// <summary>
    /// Reader-side decision for a Complex property (<c>EmitReadComplex</c>) — does the
    /// emit need a full ref-aware switch on <c>p.ChildNeedsRefScan</c>?
    /// </summary>
    internal static bool ChildEmitsRefMarker(PropInfo p) => p.ChildNeedsRefScan;

    /// <summary>
    /// Reader-side decision for a collection element (<c>EmitReadCollectionElement</c>) and
    /// writer-side decision for the same element (<c>EmitDirectCollectionWrite</c>) — keyed on
    /// <c>p.ElementNeedsRefScan</c>.
    /// </summary>
    internal static bool ElementEmitsRefMarker(PropInfo p) => p.ElementNeedsRefScan;

    /// <summary>
    /// Reader-side overload for <c>EmitReadCollectionElement</c> when only the bool flag is in
    /// scope (e.g. when <c>PropInfo</c> is unrolled at the call site). Same semantics — kept as
    /// a thin overload so EVERY call site routes through this predicate, not the raw field.
    /// </summary>
    internal static bool ElementEmitsRefMarker(bool elementNeedsRefScan) => elementNeedsRefScan;

    /// <summary>
    /// Reader-side decision for a dictionary value (<c>EmitReadDictionary</c>) — keyed on
    /// <c>p.DictValueNeedsRefScan</c>. Symmetric with the Complex / Collection-element overloads.
    /// </summary>
    internal static bool DictValueEmitsRefMarker(PropInfo p) => p.DictValueNeedsRefScan;
}
