#pragma once
#include <limine.h>
#include <cstdint>
#include <Terminal/Terminal.hpp>

namespace Memory::VMM {
    struct PageTableEntry {
        std::uint8_t Present : 1;
        std::uint8_t Writable : 1;
        std::uint8_t Supervisor : 1;
        std::uint8_t WriteThrough : 1;
        std::uint8_t CacheDisabled : 1;
        std::uint8_t Accessed : 1;
        std::uint8_t Ignore : 1;
        std::uint8_t LargerPages : 1;
        std::uint8_t PageSize : 1;
        std::uint8_t Available : 3;
        std::uint64_t Address : 52;
    };

    struct PageTableEntry40Bit {
        std::uint8_t Present : 1;
        std::uint8_t Writable : 1;
        std::uint8_t Supervisor : 1;
        std::uint8_t WriteThrough : 1;
        std::uint8_t CacheDisabled : 1;
        std::uint8_t Accessed : 1;
        std::uint8_t Ignore : 1;
        std::uint8_t LargerPages : 1;
        std::uint8_t PageSize : 1;
        std::uint8_t Available : 3;
        std::uint64_t Address : 40;
        std::uint8_t AvailableHigh : 7;
        std::uint8_t PK : 4;
        std::uint8_t NX : 1;
    };


    struct PageTable {
        PageTableEntry entries[512];
    } __attribute__((packed)) __attribute__((aligned(0x1000)));

    struct VirtualAddress {
        std::uint64_t address;

        VirtualAddress(std::uint64_t newAddress) {
            if (newAddress % 0x1000 != 0) {
                Kt::KernelLogStream(Kt::WARNING, "VMM") << "VirtualAddress object created with non-aligned value.";
            }

            address = newAddress;
        }

        uint64_t GetL4Index() {
            return (address >> 39) & 0x1ff;
        }

        uint64_t GetL3Index() {
            return (address >> 30) & 0x1ff;
        }

        uint64_t GetL2Index() {
            return (address >> 21) & 0x1ff;
        }

        uint64_t GetPageIndex() {
            return (address >> 12) & 0x1ff;
        }

        uint64_t GetIndex(size_t level) {
            if (level == 4)
                return GetL4Index();

            else if (level == 3)
                return GetL3Index();

            else if (level == 2)
                return GetL2Index();

            else if (level == 1)
                return GetPageIndex();

            return 0;
        }
    };

    class Paging {
        PageTable* HandleLevel(VirtualAddress virtualAddress, PageTable* table, size_t level);
        PageTable* HandleLevelUser(VirtualAddress virtualAddress, PageTable* table, size_t level);
public:
        PageTable* PML4{};

        Paging();
        void Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, limine_memmap_response* memMap);
        void Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
        void MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
        void MapWC(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
        void MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
        static std::uint64_t GetPhysAddr(std::uint64_t PML4, std::uint64_t virtualAddress, bool use40BitL1 = false);
        std::uint64_t GetPhysAddr(std::uint64_t virtualAddress);

        // Create a new PML4 with kernel-half (entries 256-511) copied from g_paging.
        // Returns the physical address of the new PML4.
        static std::uint64_t CreateUserPML4();

        // Map a page into an arbitrary PML4 (specified by physical address) with User bit set.
        static void MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress);

        // Map a page into an arbitrary PML4 with User + Write-Combining attributes.
        static void MapUserInWC(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress);

        // Unmap a single page from an arbitrary PML4 (clears PTE + invalidates TLB).
        static void UnmapUserIn(std::uint64_t pml4Phys, std::uint64_t virtualAddress);

        // Free all user-half page table structures and physical pages (PML4 entries 0-255).
        // Does NOT free the PML4 page itself (caller handles that).
        // Skips MMIO/WC pages (WriteThrough or CacheDisabled set in PTE).
        static void FreeUserHalf(std::uint64_t pml4Phys);

        // Identity-map EFI runtime service regions so firmware code can
        // reference its own data at physical addresses.
        void MapEfiRuntime(limine_efi_memmap_response* efiMemmap);
    };

    extern Paging* g_paging;

    extern "C" uint64_t GetCR3();
    extern "C" void LoadCR3(PageTable* PML4);

    inline void FlushTLB() {
        asm volatile("mov %%cr3, %%rax; mov %%rax, %%cr3" ::: "rax", "memory");
    }
};
