#pragma once

#include <Core/Common/CoreTypes.h>
#include <Core/Common/CoreUtils.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Core/Memory/Utils.h>
#include <Core/Memory/Utils.h>
#include <Juliet.h>
#include <typeinfo> // Added for typeid

namespace Juliet
{
    constexpr global uint64 g_Arena_Default_Reserve_Size = Megabytes(64);
    constexpr global uint64 g_Arena_Default_Commit_Size  = Kilobytes(64);
    constexpr global uint64 k_ArenaHeaderSize            = 128;

    struct ArenaFreeNode;

#if JULIET_DEBUG
    struct ArenaDebugInfo;
#endif

    struct Arena
    {
        Arena* Previous;
        Arena* Current;

        uint64 BasePosition;
        uint64 Position;
        uint64 Alignment;

        uint64 CommitSize;
        uint64 ReserveSize;

        uint64 Committed;
        uint64 Reserved;

        Arena* FreeBlockLast;

        ArenaFreeNode* FreeNodes;

        bool AllowRealloc : 1;

        JULIET_DEBUG_ONLY(uint16 LostNodeCount;)
        JULIET_DEBUG_ONLY(bool CanReserveMore : 1;)
        
        JULIET_DEBUG_ONLY(Arena* GlobalNext;)
        JULIET_DEBUG_ONLY(Arena* GlobalPrev;)
        JULIET_DEBUG_ONLY(ArenaDebugInfo* FirstDebugInfo;)
        JULIET_DEBUG_ONLY(const char* Name;)
    };
    static_assert(sizeof(Arena) <= k_ArenaHeaderSize);

    struct ArenaParams
    {
        uint64 ReserveSize = g_Arena_Default_Reserve_Size;
        uint64 CommitSize  = g_Arena_Default_Commit_Size;

        // True: All push will be 32 bytes minimum
        bool AllowRealloc = false;

        // When false, will assert if a new block is reserved.
        // Useful for Vectors as they are guaranteed to be linear and i wont need to implement memcopy to increase capacity
        JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;)
    };

    [[nodiscard]] Arena* ArenaAllocate(const ArenaParams&          params
                                       JULIET_DEBUG_ONLY(, const char* name),
                                       const std::source_location& loc    = std::source_location::current());
    void                 ArenaRelease(NonNullPtr<Arena> arena);

    // Raw Push, can be used but templated helpers exists below
    // Raw Push, can be used but templated helpers exists below
    [[nodiscard]] void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align, bool shouldBeZeroed
                                  JULIET_DEBUG_ONLY(, const char* tag));
    [[nodiscard]] void* ArenaReallocate(NonNullPtr<Arena> arena, void* oldPtr, size_t oldSize, size_t newSize,
                                        size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag));
    void                 ArenaPopTo(NonNullPtr<Arena> arena, size_t position);
    void                 ArenaPop(NonNullPtr<Arena> arena, size_t amount);
    void                 ArenaClear(NonNullPtr<Arena> arena);
    [[nodiscard]] size_t ArenaPos(NonNullPtr<Arena> arena);

    template <typename Type>
    [[nodiscard]] Type* ArenaPushStruct(NonNullPtr<Arena> arena)
    {
        return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true JULIET_DEBUG_ONLY(, typeid(Type).name())));
    }

    // #define push_array_no_zero_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align), (0))
    // #define push_array_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align), (1))
    // #define push_array_no_zero(a, T, c) push_array_no_zero_aligned(a, T, c, Max(8, AlignOf(T)))
    // #define push_array(a, T, c) push_array_aligned(a, T, c, Max(8, AlignOf(T)))

    template <typename Type>
    [[nodiscard]] Type* ArenaPushArray(NonNullPtr<Arena> arena, size_t count)
    {
        return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)), true JULIET_DEBUG_ONLY(, typeid(Type).name())));
    }

    // --- Paged Memory Architecture ---
    struct ArenaAllocation;

    struct MemoryBlock
    {
        static constexpr uint32 kMagic = 0xAA55AA55;
        uint32                  Magic;
        MemoryBlock*            Next;      // Next block in the chain (Arena) or FreeList (Pool)
        size_t                  TotalSize; // Total size of this block (including header)
        size_t                  Used;      // Offset relative to the start of Data

#if JULIET_DEBUG
        ArenaAllocation* FirstAllocation = nullptr;
        uint64           Pad; // Ensure 16-byte alignment (Size 40 -> 48)
#endif

        // Data follows immediately.
        // We use a helper to access it to avoid C++ flexible array warning issues if strict
        uint8*       GetData() { return reinterpret_cast<uint8*>(this + 1); }
        const uint8* GetData() const { return reinterpret_cast<const uint8*>(this + 1); }
    };

    struct MemoryPool
    {
        void*        BaseAddress = nullptr;
        size_t       TotalSize   = 0;
        MemoryBlock* FreeList    = nullptr;

        [[nodiscard]] MemoryBlock* AllocateBlock(size_t minCapacity);
        void                       FreeBlock(MemoryBlock* block);
    };

    struct MemoryArena
    {
        MemoryPool*  BackingPool;
        MemoryBlock* CurrentBlock;
        MemoryBlock* FirstBlock;
        // Marker behavior is now tricky with pages.
        // Simple Marker = { Block*, Offset }
    };

    struct ArenaMarker
    {
        MemoryBlock* Block;
        size_t       Offset;
    };

    JULIET_API void  MemoryArenaCreate(MemoryArena* arena, MemoryPool* pool);
    JULIET_API void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment, String tag);
    JULIET_API void* ArenaRealloc(MemoryArena* arena, void* oldPtr, size_t oldSize, size_t newSize, size_t alignment, String tag);
    JULIET_API bool        ArenaPop(MemoryArena* arena, void* ptr, size_t size);
    JULIET_API void        ArenaReset(MemoryArena* arena);
    JULIET_API ArenaMarker ArenaGetMarker(MemoryArena* arena);
    JULIET_API void        ArenaResetToMarker(MemoryArena* arena, ArenaMarker marker);

    // --- Global Arenas & Management ---

    // Returns a global arena that resets every frame.
    JULIET_API MemoryArena* GetScratchArena();

    // Persistent game arena.
    JULIET_API MemoryArena* GetGameArena();

    // Internal engine function to reset the scratch arena.
    JULIET_API void ScratchArenaReset();

    // Internal engine function to initialize memory arenas.
    void MemoryArenasInit();

    // Internal engine function to shutdown memory arenas.
    // Internal engine function to shutdown memory arenas.
    void MemoryArenasShutdown();

#if JULIET_DEBUG
    JULIET_API Arena* GetGlobalArenaListHead();
#endif


    template <typename T>
    inline T* ArenaPushType(MemoryArena* arena, String tag)
    {
        T* result = static_cast<T*>(ArenaPush(arena, sizeof(T), alignof(T), tag));

        if (result)
        {
            MemSet(result, 0, sizeof(T));
        }
        return result;
    }

    template <typename T>
    inline T* ArenaPushArray(MemoryArena* arena, size_t count, String tag)
    {
        T* result = static_cast<T*>(ArenaPush(arena, sizeof(T) * count, alignof(T), tag));

        if (result)
        {
            MemSet(result, 0, sizeof(T) * count);
        }
        return result;
    }

    template <typename T>
    inline T* ArenaRealloc(MemoryArena* arena, T* oldPtr, size_t oldCount, size_t newCount, String tag)
    {
        return static_cast<T*>(Juliet::ArenaRealloc(arena, static_cast<void*>(oldPtr), sizeof(T) * oldCount,
                                                    sizeof(T) * newCount, alignof(T), tag));
    }
} // namespace Juliet
