using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("AyCode.Services")]

namespace AyCode.Core.Serializers.Binaries;

/// <summary>
/// Binary type codes for serialization.
/// Markers 0..(SlotCount-1) are reserved for object type slot indices (FixObj).
/// All other type markers are defined relative to SlotCount.
/// </summary>
internal static class BinaryTypeCode
{
    /// <summary>
    /// Number of reserved FixObj slot markers (0..SlotCount-1).
    /// When a marker byte is less than SlotCount, it represents an object
    /// whose type wrapper is cached at _wrapperSlots[marker].
    /// All type markers are defined relative to this constant.
    /// </summary>
    public const int SlotCount = 64;

    // Complex types (SlotCount + 0..7)
    public const byte Object = SlotCount + 0;                     // 64 — Start of object (fallback when >SlotCount types)
    public const byte ObjectRef = SlotCount + 1;                  // 65 — Reference to previously serialized object (2+ occurrence)
    public const byte Array = SlotCount + 2;                      // 66 — Start of array/list
    public const byte Dictionary = SlotCount + 3;                 // 67 — Start of dictionary
    public const byte ByteArray = SlotCount + 4;                  // 68 — Optimized byte[] storage
    public const byte ObjectWithMetadata = SlotCount + 5;         // 69 — Object with metadata (UseMetadata mode)
    public const byte ObjectRefFirst = SlotCount + 6;             // 70 — First occurrence of tracked object (ref handling enabled)
    public const byte ObjectWithMetadataRefFirst = SlotCount + 7; // 71 — First occurrence of tracked object with metadata

    // Polymorphic object markers (SlotCount + 8..11)
    // Used when declared property type != runtime type AND UseMetadata=false.
    //
    // PREFIX markers (inner Object/Array/Dict marker follows):
    //   [ObjectWithTypeName] [UTF8 typeName] [Object | Array | ...] [body...]
    //   [ObjectWithTypeIndex] [VarUInt typeIndex] [Object | Array | ...] [body...]
    //
    // COMBINED markers (no inner marker — object body follows directly):
    //   [ObjectWithTypeNameRefFirst] [UTF8 typeName] [VarUInt refCacheIndex] [properties...]
    //   [ObjectWithTypeIndexRefFirst] [VarUInt typeIndex] [VarUInt refCacheIndex] [properties...]
    //
    // ObjectRef for 2+ occurrence: written directly, NO poly prefix needed.
    public const byte ObjectWithTypeName = SlotCount + 8;              // 72
    public const byte ObjectWithTypeNameRefFirst = SlotCount + 9;      // 73
    public const byte ObjectWithTypeIndex = SlotCount + 10;            // 74
    public const byte ObjectWithTypeIndexRefFirst = SlotCount + 11;    // 75

    // Primitive types (SlotCount + 12..26)
    public const byte Null = SlotCount + 12;       // 76
    public const byte True = SlotCount + 13;       // 77
    public const byte False = SlotCount + 14;      // 78
    public const byte Int8 = SlotCount + 15;       // 79
    public const byte UInt8 = SlotCount + 16;      // 80
    public const byte Int16 = SlotCount + 17;      // 81
    public const byte UInt16 = SlotCount + 18;     // 82
    public const byte Int32 = SlotCount + 19;      // 83
    public const byte UInt32 = SlotCount + 20;     // 84
    public const byte Int64 = SlotCount + 21;      // 85
    public const byte UInt64 = SlotCount + 22;     // 86
    public const byte Float32 = SlotCount + 23;    // 87
    public const byte Float64 = SlotCount + 24;    // 88
    public const byte Decimal = SlotCount + 25;    // 89
    public const byte Char = SlotCount + 26;       // 90

    // ============================================================================
    // String types
    // ============================================================================
    //
    // Marker 91 is reserved for FastWire UTF-16 payloads:
    //   [StringUtf16][charLen:int32 LE][UTF-16 raw bytes]
    //
    // Universal compact-mode strings use FixStr (135..166) + String (167):
    //   [FixStr] / [String][VarUInt(charLength - FixStrCount)][unsigned excess slot][UTF-8 bytes]
    //
    // Interning tiers keep dedicated markers.
    public const byte StringUtf16 = SlotCount + 27;       // 91 — FastWire UTF-16 marker payload
    public const byte StringInterned = SlotCount + 28;    // 92 — Reference to interned string by index (2+ occurrence)
    public const byte StringEmpty = SlotCount + 29;       // 93 — Empty string marker

    // Date/Time types (SlotCount + 31..34)
    public const byte DateTime = SlotCount + 31;         // 95
    public const byte DateTimeOffset = SlotCount + 32;   // 96
    public const byte TimeSpan = SlotCount + 33;         // 97
    public const byte Guid = SlotCount + 34;             // 98

    // Enum (SlotCount + 35)
    public const byte Enum = SlotCount + 35;             // 99

    // Legacy header markers (SlotCount + 36..37)
    public const byte MetadataHeader = SlotCount + 36;    // 100 — Binary has metadata section (legacy, implies HasReferenceHandling=true)
    public const byte NoMetadataHeader = SlotCount + 37;  // 101 — Binary has no metadata (legacy, implies HasReferenceHandling=true)

    // Property skip marker (SlotCount + 38)
    public const byte PropertySkip = SlotCount + 38;     // 102 — Marks a property with default/null value (skipped during serialization)

    // ============================================================================
    // Reserved/Extended marker range (post 2026-05-06 H2Q6 marker reorg, v3 wire format)
    // ============================================================================
    //
    // Range 103..134 (32 values) was previously the FixStr range (UTF-8 short, byteLength 0-31).
    // FixStr (non-ASCII) markers REMOVED in H2Q6 — non-ASCII strings now use Small/Medium/Big tiers
    // for 1-pass decode (eliminated CountUtf8Chars Pass 1).
    //
    // CURRENT ALLOCATION:
    public const byte StringInternFirstSmall = SlotCount + 40;  // 104 — Interning tier 1: [marker:1][cacheIdx:VarUInt][charLen:8][utf8Len:8][bytes]
    public const byte StringInternFirstMedium = SlotCount + 41; // 105 — Interning tier 2: [marker:1][cacheIdx:VarUInt][charLen:16][utf8Len:16][bytes]

    // RESERVED (29 values: 106..134) — strategic future-feature reservation per BINARY_TODO.md H2Q6 marker address space plan:
    //   106..121 (16 values): ACCORE-BIN-T-L9Y3 — FixArray short-list count in marker (count 0-15)
    //   122..126 (5 values):  ACCORE-BIN-T-S5L8 — sentinel-length encoding tiers
    //   127..130 (4 values):  ACCORE-BIN-T-S2X9 — markerless schema lane opt-in
    //   131       (1 value):  ACCORE-BIN-T-F3W6 — dedicated FastWire string marker (split mode-shared StringSmall)
    //   132..134 (3 values):  general reserve
    //
    // Readers MUST throw "unknown marker" on any value in 106..134 until the corresponding feature
    // activates within the v3 wire format envelope (no further wire-format break needed).
    public const byte ReservedRangeMin = SlotCount + 42; // 106 — first reserved value (post-H2Q6 future-feature range)
    public const byte ReservedRangeMax = SlotCount + 70; // 134 — last reserved value

    // FixStr range (short universal string marker): 135..166 (32 values for char lengths 0-31)
    // Encoding: FixStrBase + charLength
    public const byte FixStrBase = SlotCount + 71;  // 135
    public const byte FixStrMax = FixStrBase + 31; // 166
    public const int FixStrMaxLength = 31;
    /// <summary>
    /// Number of FixStr marker slots (= <see cref="FixStrMaxLength"/> + 1 = 32). Also used as the
    /// wire-format offset for the long-form <see cref="String"/> marker's VarUInt-encoded charLength:
    /// <c>wireLen = charLength - FixStrCount</c>. Single source of truth — if the FixStr range
    /// ever expands (e.g. 32 → 64 slots), this constant updates the offset everywhere consistently.
    /// </summary>
    public const int FixStrCount = FixStrMaxLength + 1; // 32
    // Backward-compatible aliases (old naming)
    public const byte FixStrAsciiBase = FixStrBase;
    public const byte FixStrAsciiMax = FixStrMax;
    public const int FixStrAsciiMaxLength = FixStrMaxLength;

    // Single universal long-form string marker (formerly split into StringLen8/16/32 at 167/168/169 —
    // unified via prefix-tier VarUInt charLength encoding; magnitude-tier-elés is exactly what VarUInt
    // does, so the 3-way marker discrimination became redundant).
    //
    // Wire layout: [String:1] [VarUInt(charLength - FixStrCount)] [excess slot:1|2|4] [UTF-8 bytes]
    //
    // charLength here is always > FixStrMaxLength (smaller values use FixStr).
    public const byte String = SlotCount + 103;  // 167

    // 168..169 — Reserved (formerly StringLen16/32, freed by VarUInt-based length unification).
    // Available for future marker allocation per BINARY_TODO.md marker-tier reorganization plan.
    public const byte ReservedStringSlotMin = SlotCount + 104; // 168
    public const byte ReservedStringSlotMax = SlotCount + 105; // 169

    // Reserved slot block: 170..175 (6 slots) for future string-related markers
    // (e.g., StringCompressed, StringEncoded, StringMixedAscii, etc.). Keeping the 135..169 range
    // dedicated to universal compact string markers for clean range-checks (see IsString below).

    // Flag-based header markers (must be 16-aligned for flag bits in lower nibble).
    // Header byte structure: (marker & 0xF0) == HeaderFlagsBase, flags in (marker & 0x0F).
    // Moved from 144 → 176 (next 16-aligned value after the new ASCII string range) to keep all
    // string-related markers in one contiguous block 91..167 / FixStrBase..StringAscii.
    public const byte HeaderFlagsBase = 176;             // 0xB0 — next 16-aligned value after StringAscii reserved block
    public const byte HeaderFlag_Metadata = 0x01;                  // Bit 0: property metadata included
    // Reference handling uses 2 separate bits:
    // Bit 1 (0x02): OnlyId - reference handling for IId objects only
    // Bit 2 (0x04): All - reference handling for all objects (includes OnlyId)
    // None = both false, OnlyId = 0x02, All = 0x06 (both bits set)
    public const byte HeaderFlag_RefHandling_OnlyId = 0x02;
    public const byte HeaderFlag_RefHandling_All = 0x04;
    public const byte HeaderFlag_HasFooterPosition = 0x08;         // Bit 3: 4-byte footer position follows flags (legacy)
    public const byte HeaderFlag_HasCacheCount = 0x08;             // Bit 3 (reused): VarUInt cache count follows flags (new marker-based format)

    // Compact integer variants (unchanged)
    public const byte Int32Tiny = 192;       // -16 to 47 stored in single byte (value = code - 192 - 16)
    public const byte Int32TinyMax = 255;    // Upper bound for tiny int (192 + 64 - 1 = 255)

    /// <summary>
    /// Largest <c>string.Length</c> value safe for binary serialization — <c>charLength * 4</c> (UTF-8
    /// worst-case byte count) MUST fit in <c>int</c>. Above this, the writer's <c>maxBytes = charLength * 4</c>
    /// computation overflows: at exactly <c>0x40000000</c> chars it wraps to 0 (silent zero-overflow → wire
    /// emits empty string with the original charLength claim, silent data loss). The single
    /// <c>(uint)charLength &gt; MaxStringCharLength</c> guard catches the overflow band cheaply
    /// (one unsigned compare on the writer hot path, predict-friendly — always false on realistic input).
    /// </summary>
    public const int MaxStringCharLength = 0x1FFFFFFF;  // 536_870_911 — largest charLength where charLength * 4 fits in int

    /// <summary>
    /// Check if type code represents a reference (string or object).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsReference(byte typeCode) => typeCode is StringInterned or ObjectRef;

    /// <summary>
    /// Check if type code is any string-related marker — H2Q6 non-ASCII tiers (Small/Medium/Big),
    /// H2Q6 interning tiers (InternFirstSmall/Medium), interning ref (StringInterned), empty marker,
    /// or any ASCII variant (FixStrAscii / StringAscii). Centralized predicate so adding/removing
    /// string markers requires updating only this method, not every dispatch site.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsString(byte typeCode)
        => typeCode == StringUtf16
        || typeCode == StringInterned
        || typeCode == StringEmpty
        || typeCode == StringInternFirstSmall
        || typeCode == StringInternFirstMedium
        || (typeCode is >= FixStrBase and <= String);          // 135..167: FixStr + String

    /// <summary>
    /// Check if type code is the FastWire UTF-16 string marker.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsStringUtf16(byte typeCode) => typeCode == StringUtf16;

    /// <summary>
    /// Check if type code is a H2Q6 interning first-occurrence tier marker (StringInternFirstSmall / Medium).
    /// (Big tier never engages on the interning path — see BinaryTypeCode header comment for rationale.)
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsStringInternFirst(byte typeCode)
        => typeCode == StringInternFirstSmall || typeCode == StringInternFirstMedium;

    /// <summary>
    /// Check if type code is any universal string marker — FixStr (short, 135..166) or String (long, 167).
    /// Single contiguous range (135..167) for branch-friendly dispatch on the reader hot path.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsStringUniversalMarker(byte typeCode) => typeCode is >= FixStrBase and <= String;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsAsciiString(byte typeCode) => IsStringUniversalMarker(typeCode);

    /// <summary>
    /// Check if type code is a FixStr (short universal marker with char length encoded in type code).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsFixStr(byte typeCode) => typeCode is >= FixStrBase and <= FixStrMax;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsFixStrAscii(byte typeCode) => IsFixStr(typeCode);

    /// <summary>
    /// Decode FixStr char length from type code.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int DecodeFixStrLength(byte typeCode) => typeCode - FixStrBase;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int DecodeFixStrAsciiLength(byte typeCode) => DecodeFixStrLength(typeCode);

    /// <summary>
    /// Encode FixStr type code for given char length (0-31).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte EncodeFixStr(int charLength) => (byte)(FixStrBase + charLength);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte EncodeFixStrAscii(int charLength) => EncodeFixStr(charLength);

    /// <summary>
    /// Check if char length can be encoded as FixStr (short string, 0..31 chars).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool CanEncodeAsFixStr(int charLength) => charLength is >= 0 and <= 31;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool CanEncodeAsFixStrAscii(int charLength) => CanEncodeAsFixStr(charLength);

    /// <summary>
    /// Universal string excess-slot width selector shared by writer and reader.
    ///
    /// Current wire contract uses UNSIGNED excess (ASCII=0, UTF8>0), so the slot thresholds are:
    /// 1-byte: max excess 255 => charLength <= 127 (worst-case excess = 2 * charLength)
    /// 2-byte: max excess 65535 => charLength <= 32767
    /// 4-byte: fallback
    ///
    /// IMPORTANT: if the protocol switches back to SIGNED excess (e.g., UTF16 discriminator via negative
    /// values), these thresholds MUST be reduced here as well (typically 63 / 16383 / 4-byte fallback).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int GetUniversalStringExcessSlotSize(int charLength)
    {
        if (charLength <= 127) return 1;
        if (charLength <= 32767) return 2;
        return 4;
    }

    /// <summary>
    /// Check if type code is a tiny int (single byte int32 encoding).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsTinyInt(byte typeCode) => typeCode >= Int32Tiny;

    /// <summary>
    /// Decode tiny int value from type code.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int DecodeTinyInt(byte typeCode) => typeCode - Int32Tiny - 16;

    /// <summary>
    /// Encode small int value (-16 to 47) as type code.
    /// Returns true if value fits in tiny encoding.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool TryEncodeTinyInt(int value, out byte typeCode)
    {
        // Range: -16 to 47 (64 values total, fitting in 192-255)
        if (value is >= -16 and <= 47)
        {
            typeCode = (byte)(value + 16 + Int32Tiny);
            return true;
        }
        typeCode = 0;
        return false;
    }
}
