local PLUGIN = PLUGIN

util.AddNetworkString("ixStorageOpen")
util.AddNetworkString("ixStorageDeposit")
util.AddNetworkString("ixStorageWithdraw")
util.AddNetworkString("ixStorageSync")

-- Robust and STABLE data retrieval: ensures we ALWAYS return a sequential numeric array
-- handled in a stable order based on original keys.
function PLUGIN:GetStorage(char)
    local data = char:GetData("personal_storage", {})
    if (!istable(data)) then return {} end
    
    local fixedData = {}
    local keys = {}
    for k, _ in pairs(data) do
        local n = tonumber(k)
        if (n) then table.insert(keys, n) end
    end
    table.sort(keys) -- Ensure key stability (1 before 2, etc)
    
    for _, k in ipairs(keys) do
        local v = data[k] or data[tostring(k)]
        if (istable(v) and v.type) then
            table.insert(fixedData, v)
        end
    end
    
    return fixedData
end

function PLUGIN:SaveStorage(char, data)
    -- We save it as a clean array. Helix will JSON-encode it.
    char:SetData("personal_storage", data)
end

net.Receive("ixStorageDeposit", function(len, ply)
    local char = ply:GetCharacter()
    if (!char) then return end

    local type = net.ReadString()
    local id = net.ReadString()
    
    local storage = PLUGIN:GetStorage(char)
    local entry = {type = type}

    if (type == "weapon") then
        local weapon = ply:GetWeapon(id)
        if (!IsValid(weapon)) then 
            net.Start("ixStorageSync")
            net.WriteTable(storage)
            net.Send(ply)
            return 
        end

        local blacklist = {["weapon_physgun"] = true, ["gmod_tool"] = true, ["gmod_camera"] = true}
        if (blacklist[id]) then 
             net.Start("ixStorageSync")
             net.WriteTable(storage)
             net.Send(ply)
             return 
        end

        entry.class = id
        entry.name = weapon:GetPrintName() or id
        entry.clip1 = weapon:Clip1()
        entry.clip2 = weapon:Clip2()
        
        ply:StripWeapon(id)
        
        table.insert(storage, entry)
        PLUGIN:SaveStorage(char, storage)
        
        net.Start("ixStorageSync")
        net.WriteTable(storage)
        net.Send(ply)
    elseif (type == "item") then
        local inv = char:GetInventory()
        local itemID = tonumber(id)
        local item = inv:GetItemByID(itemID)
        
        if (!item) then 
            net.Start("ixStorageSync")
            net.WriteTable(storage)
            net.Send(ply)
            return 
        end

        entry.uniqueID = item.uniqueID
        entry.data = item.data or {}
        entry.name = item:GetName() or item.name
        
        item:Remove():next(function()
            -- Add to storage only after successful removal from inventory
            local currentStorage = PLUGIN:GetStorage(char)
            table.insert(currentStorage, entry)
            PLUGIN:SaveStorage(char, currentStorage)
            
            net.Start("ixStorageSync")
            net.WriteTable(currentStorage)
            net.Send(ply)
        end):catch(function(err)
            net.Start("ixStorageSync")
            net.WriteTable(PLUGIN:GetStorage(char))
            net.Send(ply)
            ply:Notify("Ошибка удаления: " .. (err or "неизвестно"))
        end)
    end
end)

net.Receive("ixStorageWithdraw", function(len, ply)
    local char = ply:GetCharacter()
    if (!char) then return end

    local index = net.ReadUInt(8)
    local storage = PLUGIN:GetStorage(char)
    local entry = storage[index]
    
    if (!entry) then 
        net.Start("ixStorageSync")
        net.WriteTable(storage)
        net.Send(ply)
        return 
    end

    -- PRE-EMPTIVE REMOVAL to prevent duplication exploit/bug
    table.remove(storage, index)
    PLUGIN:SaveStorage(char, storage)

    if (entry.type == "weapon") then
        local weapon = ply:Give(entry.class)
        if (IsValid(weapon)) then
            weapon:SetClip1(entry.clip1 or 0)
            weapon:SetClip2(entry.clip2 or 0)
            
            -- Already removed from storage, just sync
            net.Start("ixStorageSync")
            net.WriteTable(storage)
            net.Send(ply)
        else
            -- FAIL: Put back
            local currentStorage = PLUGIN:GetStorage(char)
            table.insert(currentStorage, entry)
            PLUGIN:SaveStorage(char, currentStorage)
            
            net.Start("ixStorageSync")
            net.WriteTable(currentStorage)
            net.Send(ply)
        end
    else
        local inv = char:GetInventory()
        inv:Add(entry.uniqueID, 1, entry.data):next(function(res)
            -- Already removed from storage, just sync to confirm removal
            net.Start("ixStorageSync")
            net.WriteTable(PLUGIN:GetStorage(char))
            net.Send(ply)
        end):catch(function(err)
            -- FAIL: Put back
            local currentStorage = PLUGIN:GetStorage(char)
            table.insert(currentStorage, entry)
            PLUGIN:SaveStorage(char, currentStorage)
            
            net.Start("ixStorageSync")
            net.WriteTable(currentStorage)
            net.Send(ply)
            
            ply:Notify("Нет места в инвентаре! Предмет возвращен в шкаф.")
        end)
    end
end)

function PLUGIN:OpenStorage(ply)
    local char = ply:GetCharacter()
    if (!char) then return end

    local data = self:GetStorage(char)
    net.Start("ixStorageSync")
    net.WriteTable(data)
    net.Send(ply)
end
