using System.Buffers.Binary;
using System.Runtime.InteropServices;

namespace ZB.MOM.WW.CBDD.Core.Storage;

/// <summary>
///     Header for slotted pages supporting multiple variable-size documents per page.
///     Fixed 24-byte structure at start of each data page.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 24)]
public struct SlottedPageHeader
{
    /// <summary>Page ID</summary>
    [FieldOffset(0)] public uint PageId;

    /// <summary>Type of page (Data, Overflow, Index, Metadata)</summary>
    [FieldOffset(4)] public PageType PageType;

    /// <summary>Number of slot entries in this page</summary>
    [FieldOffset(8)] public ushort SlotCount;

    /// <summary>Offset where free space starts (grows down with slots)</summary>
    [FieldOffset(10)] public ushort FreeSpaceStart;

    /// <summary>Offset where free space ends (grows up with data)</summary>
    [FieldOffset(12)] public ushort FreeSpaceEnd;

    /// <summary>Next overflow page ID (0 if none)</summary>
    [FieldOffset(14)] public uint NextOverflowPage;

    /// <summary>Transaction ID that last modified this page</summary>
    [FieldOffset(18)] public uint TransactionId;

    /// <summary>Reserved for future use</summary>
    [FieldOffset(22)] public ushort Reserved;

    public const int Size = 24;

    /// <summary>
    ///     Initializes a header with the current slotted-page format marker.
    /// </summary>
    public SlottedPageHeader()
    {
        this = default;
        Reserved = StorageFormatConstants.SlottedPageFormatMarker;
    }

    /// <summary>
    ///     Gets available free space in bytes
    /// </summary>
    public readonly int AvailableFreeSpace => FreeSpaceEnd - FreeSpaceStart;

    /// <summary>
    ///     Writes header to span
    /// </summary>
    /// <param name="destination">The destination span that receives the serialized header.</param>
    public readonly void WriteTo(Span<byte> destination)
    {
        if (destination.Length < Size)
            throw new ArgumentException($"Destination must be at least {Size} bytes");

        MemoryMarshal.Write(destination, in this);
    }

    /// <summary>
    ///     Reads header from span
    /// </summary>
    /// <param name="source">The source span containing the serialized header.</param>
    public static SlottedPageHeader ReadFrom(ReadOnlySpan<byte> source)
    {
        if (source.Length < Size)
            throw new ArgumentException($"Source must be at least {Size} bytes");

        return MemoryMarshal.Read<SlottedPageHeader>(source);
    }
}

/// <summary>
///     Slot entry pointing to a document within a page.
///     Fixed 8-byte structure in slot array.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 8)]
public struct SlotEntry
{
    /// <summary>Offset to document data within page</summary>
    [FieldOffset(0)] public ushort Offset;

    /// <summary>Length of document data in bytes</summary>
    [FieldOffset(2)] public ushort Length;

    /// <summary>Slot flags (deleted, overflow, etc.)</summary>
    [FieldOffset(4)] public SlotFlags Flags;

    public const int Size = 8;

    /// <summary>
    ///     Writes slot entry to span
    /// </summary>
    /// <param name="destination">The destination span that receives the serialized slot entry.</param>
    public readonly void WriteTo(Span<byte> destination)
    {
        if (destination.Length < Size)
            throw new ArgumentException($"Destination must be at least {Size} bytes");

        MemoryMarshal.Write(destination, in this);
    }

    /// <summary>
    ///     Reads slot entry from span
    /// </summary>
    /// <param name="source">The source span containing the serialized slot entry.</param>
    public static SlotEntry ReadFrom(ReadOnlySpan<byte> source)
    {
        if (source.Length < Size)
            throw new ArgumentException($"Source must be at least {Size} bytes");

        return MemoryMarshal.Read<SlotEntry>(source);
    }
}

/// <summary>
///     Flags for slot entries
/// </summary>
[Flags]
public enum SlotFlags : uint
{
    /// <summary>Slot is active and contains data</summary>
    None = 0,

    /// <summary>Slot is marked as deleted (can be reused)</summary>
    Deleted = 1 << 0,

    /// <summary>Document continues in overflow pages</summary>
    HasOverflow = 1 << 1,

    /// <summary>Document data is compressed</summary>
    Compressed = 1 << 2
}

/// <summary>
///     Location of a document within the database.
///     Maps ObjectId to specific page and slot.
/// </summary>
public readonly struct DocumentLocation
{
    /// <summary>
    ///     Gets the page identifier containing the document.
    /// </summary>
    public uint PageId { get; init; }

    /// <summary>
    ///     Gets the slot index within the page.
    /// </summary>
    public ushort SlotIndex { get; init; }

    /// <summary>
    ///     Initializes a new instance of the <see cref="DocumentLocation" /> struct.
    /// </summary>
    /// <param name="pageId">The page identifier containing the document.</param>
    /// <param name="slotIndex">The slot index within the page.</param>
    public DocumentLocation(uint pageId, ushort slotIndex)
    {
        PageId = pageId;
        SlotIndex = slotIndex;
    }

    /// <summary>
    ///     Serializes DocumentLocation to a byte span (6 bytes: 4 for PageId + 2 for SlotIndex)
    /// </summary>
    /// <param name="destination">The destination span that receives the serialized value.</param>
    public void WriteTo(Span<byte> destination)
    {
        if (destination.Length < 6)
            throw new ArgumentException("Destination must be at least 6 bytes", nameof(destination));

        BinaryPrimitives.WriteUInt32LittleEndian(destination, PageId);
        BinaryPrimitives.WriteUInt16LittleEndian(destination.Slice(4), SlotIndex);
    }

    /// <summary>
    ///     Deserializes DocumentLocation from a byte span (6 bytes)
    /// </summary>
    /// <param name="source">The source span containing the serialized value.</param>
    public static DocumentLocation ReadFrom(ReadOnlySpan<byte> source)
    {
        if (source.Length < 6)
            throw new ArgumentException("Source must be at least 6 bytes", nameof(source));

        uint pageId = BinaryPrimitives.ReadUInt32LittleEndian(source);
        ushort slotIndex = BinaryPrimitives.ReadUInt16LittleEndian(source.Slice(4));

        return new DocumentLocation(pageId, slotIndex);
    }

    /// <summary>
    ///     Size in bytes when serialized
    /// </summary>
    public const int SerializedSize = 6;
}