BaseWars = BaseWars or {}
BaseWars.SpawnMenu = BaseWars.SpawnMenu or {}
BaseWars.SpawnMenu.FlattenedShop = BaseWars.SpawnMenu.FlattenedShop or {}

BaseWars.SpawnMenu.Net = BaseWars.SpawnMenu.Net or {
    BuyEntity = "BaseWars_BuyEntity",
    AutoBuy = "BaseWars_AutoBuy"
}

BaseWars.Net.Register(BaseWars.SpawnMenu.Net.BuyEntity, { uuid = "string" })
BaseWars.Net.Register(BaseWars.SpawnMenu.Net.AutoBuy, { bool = "boolean", weapon = "string" })

BaseWars.SpawnMenu.EntityCountFormat = "BaseWars_EntityCount_%s"

-- This function computes a hash value for a given input string using a basic algorithm.
-- The hash is generated by iterating over each character in the string, updating the hash value.
local function stringHash(input)
    local hash = 0
    local len = #input

    -- Iterate over each character in the input string
    for i = 1, len do
        local char = string.byte(input, i)
        
        -- Update the hash using a simple formula and ensure it remains within 32-bit boundaries
        hash = (hash * 31 + char) % 0xFFFFFFFF
    end

    return hash
end

/*
    @description
    Generates a UUID for a given item. Using the item's properties, a UUID is generated using a simple algorithm.
    The UUID is generated using a template and a seeded pseudo-random number generator.
    Which means both client and server will generate the same UUID for the same item.
    It is useful for identifying items in the shop, and it can also prevent duplicate items from being added to the shop.
    Futhermore if someone tries manually launching fake items, the UUID will be different and the server will reject it.
    Was a problem in the original basewars gamemode the server could get confuse and would accept the fake item in very rare cases.

    @param {table} item - The item to generate a UUID for.

    @return {string} The generated UUID.
*/
function BaseWars.SpawnMenu.GenerateUUID(item)
    -- Convert the item properties into a single concatenated string
    local itemStr = item.Name .. item.Price .. item.Model .. item.ClassName

    -- Compute a hash from the concatenated string to seed the UUID generation
    local seed = stringHash(itemStr)
    
    -- A simple seeded pseudo-random number generator
    local function seededRandom()
        -- Update the seed using a basic linear congruential generator formula
        seed = (seed * 1103515245 + 12345) % 0x10000
        -- Normalize the seed value to a range of [0, 1)
        return (bit.band(seed, 0x7FFF)) / 0x7FFF
    end

    -- Using the seeded random function, replace 'x' and 'y' characters in the UUID template to generate the UUID
    -- The 4 in the third group ensures the UUID is of version 4 (not necessary, but it's cool to have a version 4 UUID!)
    local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'

    return string.gsub(template, '[xy]', function (c)
        local r = seededRandom()
        if c == 'x' then
            return string.format('%x', math.floor(r * 16))
        else
            return string.format('%x', 8 + math.floor(r * 4))
        end
    end)
end

/*
    @description
    Adds UUIDs to the shop's item entries.
    The UUIDs are generated using the item's properties, and are used to uniquely identify items in the shop.

    @param {table} categoryTable - The table representing the shop's categories.

    @return {table} The updated category table.
*/
function BaseWars.SpawnMenu.AddUUIDsToShop(categoryTable)
    local newTable = {}

    -- Iterate over each entry in the category table
    for key, value in pairs(categoryTable) do
        -- Check if the value represents an item (by the presence of a ClassName)
        if value.ClassName then
            local UUID = BaseWars.SpawnMenu.GenerateUUID(value)

            BaseWars.Log("Generated UUID " .. UUID .. " for item " .. value.Name)

            -- Copy the item to the new table, adding the UUID
            newTable[UUID] = value
        -- If the value is a table but doesn't represent an item, treat it as a category or subcategory
        elseif type(value) == "table" and not value.ClassName then
            --newTable[key] = BaseWars.SpawnMenu.AddUUIDsToShop(value.Items)  -- Recursively process the subcategory
            newTable[key] = {
                Name = value.Name,
                Icon = value.Icon,
            }

            -- Recursively process the subcategories in order to add UUIDs to their items
            local subItems = BaseWars.SpawnMenu.AddUUIDsToShop(value.Items)

            for subKey, item in pairs(subItems) do
                newTable[key][subKey] = item
            end
        else
            -- Any other type of value is directly copied to the new table
            newTable[key] = value
        end
    end
    
    return newTable
end

BaseWars.Config.Shop = BaseWars.SpawnMenu.AddUUIDsToShop(BaseWars.Config.Shop)

/*
    @description
    Collects all items from the shop's category table, recursively.
    Go through each category and subcategory, collecting all items into a single table.

    @param {table} categoryTable - The table representing the shop's categories.
    @param {number} depth - The maximum depth to recurse to.

    @return {table} A table containing all items from the shop's category table.
*/
function BaseWars.SpawnMenu.CollectItems(categoryTable, depth)
    -- If the depth limit is reached, stop the recursion
    if depth <= 0 then return {} end

    local items = {}

    -- Iterate over each entry in the category table
    for key, categoryOrItem in pairs(categoryTable) do
        if type(categoryOrItem) ~= "table" then continue end
        
        -- If the entry has a Price, treat it as an item
        if categoryOrItem.Price then
            items[key] = categoryOrItem
        else
            -- Otherwise, treat it as a subcategory and recursively collect items from it
            local subItems = BaseWars.SpawnMenu.CollectItems(categoryOrItem, depth - 1)
            for subKey, item in pairs(subItems) do
                items[subKey] = item
            end
        end
    end

    return items
end

/*
    @description
    Flattens the shop's category table into a single table containing all items.
    The table is stored in BaseWars.SpawnMenu.FlattenedShop. Useful for searching and filtering items.
    This is stored in a separate table to avoid modifying the original shop table. Also, it's only computed once. (normally)
    The client can use this table to display the shop's items, and the server can use it to validate purchases fastly.

    @return {table} A table containing all items from the shop's category table.
*/
function BaseWars.FlattenShop()
    -- Using an arbitrary depth of 10 ensures all items are collected, even from deeply nested categories
    local items = BaseWars.SpawnMenu.CollectItems(BaseWars.Config.Shop, 10)
    BaseWars.SpawnMenu.FlattenedShop = items
end

BaseWars.FlattenShop()

function BaseWars.SpawnMenu.GetWeaponAutoBuy(ply)
    return ply:GetNWBool("BaseWars_AutoBuy"), ply:GetNWString("BaseWars_AutoBuyWeapon")
end

local Player = FindMetaTable("Player")

function Player:GetBuyEntityBlockReason(uuid)
    local entityTable = BaseWars.SpawnMenu.FlattenedShop[uuid]
    if not entityTable then return "Entity does not exist" end

    local vip = entityTable.VIP
    if vip and not self:IsVIP() then return "VIP only", BaseWars.Color("YELLOW") end

    local level = entityTable.Level
    if level and level > self:GetLevel() then return "Level: " .. level, BaseWars.Color("RED") end

    return false
end

function Player:IsVIP()
    return false
end

function Player:GetIsWeaponAutoBuy()
    return self:GetNWBool("BaseWars_AutoBuy")
end

function Player:GetWeaponAutoBuy()
    return self:GetNWString("BaseWars_AutoBuyWeapon")
end

function Player:GetEntityCount(class)
    return self:GetNWInt(string.format(BaseWars.SpawnMenu.EntityCountFormat, class))
end