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 — H2Q6 layout (post 2026-05-06 marker reorg, wire format v3)
    // ============================================================================
    //
    // Non-ASCII strings use fixed-width header tier markers (NO VarUInt utf8Len),
    // enabling 1-pass decode (CountUtf8Chars Pass 1 eliminated).
    //
    // Tier dispatch (writer chooses smallest fitting tier based on utf8Len):
    //   StringSmall  — utf8Len ≤ 255       — header: 1 marker + 1 charLen + 1 utf8Len = 3 byte
    //   StringMedium — utf8Len ≤ 65535     — header: 1 marker + 2 charLen + 2 utf8Len = 5 byte
    //   StringBig    — utf8Len > 65535     — header: 1 marker + 4 charLen + 4 utf8Len = 9 byte
    //
    // Interning tiers (writer chooses based on utf8Len; Big never engages — MaxStringInternLength
    // is byte-typed (max 255 char × max 4 byte/char = 1020 byte fits in Medium):
    //   StringInternFirstSmall   — utf8Len ≤ 255    — header: 1 + cacheIdx-VarUInt + 1 + 1
    //   StringInternFirstMedium  — utf8Len ≤ 65535  — header: 1 + cacheIdx-VarUInt + 2 + 2
    //
    // ASCII strings continue to use FixStrAscii (135..166) and StringAscii (167) — unchanged from M3R7.
    //
    // String types (SlotCount + 27..30)
    public const byte StringSmall = SlotCount + 27;       // 91 — Non-ASCII tier 1: [marker:1][charLen:8][utf8Len:8][bytes], utf8Len ≤ 255
    public const byte StringInterned = SlotCount + 28;    // 92 — Reference to interned string by index (2+ occurrence) — UNCHANGED
    public const byte StringEmpty = SlotCount + 29;       // 93 — Empty string marker — UNCHANGED
    public const byte StringMedium = SlotCount + 30;      // 94 — Non-ASCII tier 2: [marker:1][charLen:16][utf8Len:16][bytes], utf8Len ≤ 65535

    // 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 (5 of 32 used):
    public const byte StringBig = SlotCount + 39;               // 103 — Non-ASCII tier 3: [marker:1][charLen:32][utf8Len:32][bytes], utf8Len > 65535
    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

    // FixStrAscii range (ASCII-only short strings): 135..166 (32 values for byte lengths 0-31)
    // FixStrAscii encoding: FixStrAsciiBase + byteLength
    // Content semantics: pure ASCII bytes (every byte < 0x80). Reader can use byte→char widening
    // without UTF-8 decode or ASCII validation — the marker itself is the validation contract.
    // Writer emits this when it can prove the content is ASCII (e.g., GetBytes returns byteCount == charLength).
    public const byte FixStrAsciiBase = SlotCount + 71;  // 135
    public const byte FixStrAsciiMax = FixStrAsciiBase + 31; // 166
    public const int FixStrAsciiMaxLength = 31;

    // Long ASCII string marker: 167
    // Layout: [StringAscii] [VarUInt byteCount] [ASCII bytes]
    // Counterpart to StringSmall/Medium/Big — but ASCII content (charLen == byteCount, no UTF-8 decode).
    // Reader fast-widens via byte→char without UTF-8 decode or IsValid scan.
    public const byte StringAscii = SlotCount + 103;     // 167

    // Reserved slot block: 168..175 (8 slots) for future string-related markers
    // (e.g., StringCompressed, StringEncoded, StringMixedAscii, etc.). Keeping the 135..167 range
    // dedicated to ASCII variants for clean range-checks (see IsAsciiString 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 is >= StringSmall and <= StringMedium)              // 91..94: StringSmall, StringInterned, StringEmpty, StringMedium
        || (typeCode is >= StringBig and <= StringInternFirstMedium)     // 103..105: StringBig, StringInternFirstSmall, StringInternFirstMedium
        || (typeCode is >= FixStrAsciiBase and <= StringAscii);          // 135..167: FixStrAscii + StringAscii

    /// <summary>
    /// Check if type code is one of the H2Q6 non-ASCII string tier markers (StringSmall / StringMedium / StringBig).
    /// Excludes interning tier markers (use <see cref="IsStringInternFirst"/>) and ASCII markers (use <see cref="IsAsciiString"/>).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsStringTier(byte typeCode)
        => typeCode == StringSmall || typeCode == StringMedium || typeCode == StringBig;

    /// <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 ASCII string marker — FixStrAscii (short) or StringAscii (long).
    /// Single contiguous range (135..167) for branch-friendly dispatch on the reader hot path.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsAsciiString(byte typeCode) => typeCode is >= FixStrAsciiBase and <= StringAscii;

    /// <summary>
    /// Check if type code is a FixStrAscii (ASCII short string with byte length encoded in type code).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsFixStrAscii(byte typeCode) => typeCode is >= FixStrAsciiBase and <= FixStrAsciiMax;

    /// <summary>
    /// Decode FixStrAscii byte length from type code. Length is also the char count (1 byte = 1 char for ASCII).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int DecodeFixStrAsciiLength(byte typeCode) => typeCode - FixStrAsciiBase;

    /// <summary>
    /// Encode FixStrAscii type code for given byte length (0-31). Caller asserts ASCII content semantics
    /// (every byte less than 0x80). Misuse on non-ASCII content corrupts decode.
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte EncodeFixStrAscii(int byteLength) => (byte)(FixStrAsciiBase + byteLength);

    /// <summary>
    /// Check if byte length can be encoded as FixStrAscii (ASCII short string, 0..31 bytes).
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool CanEncodeAsFixStrAscii(int byteLength) => byteLength is >= 0 and <= 31;

    /// <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;
    }
}
