require "lua_timers"

local ItemsController = require("TOC/Controllers/ItemsController")
local StaticData = require("TOC/StaticData")
local CommandsData = require("TOC/CommandsData")
-------------------------------

---@param zombie IsoZombie|IsoGameCharacter|IsoMovingObject|IsoObject
---@return integer trueID
local function GetZombieID(zombie)
    -- Big love to Chuck and Sir Doggy Jvla for this code
    ---@diagnostic disable-next-line: param-type-mismatch
    local pID = zombie:getPersistentOutfitID()
    local bits = string.split(string.reverse(Long.toUnsignedString(pID, 2)), "")
    while #bits < 16 do bits[#bits + 1] = 0 end

    -- trueID
    bits[16] = 0
    local trueID = Long.parseUnsignedLong(string.reverse(table.concat(bits, "")), 2)

    return trueID
end

-------------------------------


---@param item InventoryItem
local function PredicateAmputationItems(item)
    return item:getType():contains("Amputation_")
end

---@param item InventoryItem
local function PredicateAmputationItemLeft(item)
    return item:getType():contains("Amputation_") and item:getType():contains("_L")
end

---@param item InventoryItem
local function PredicateAmputationItemRight(item)
    return item:getType():contains("Amputation_") and item:getType():contains("_R")
end

---@param zombie IsoZombie
local function SpawnAmputation(zombie, side)
    local index = ZombRand(1, #StaticData.PARTS_STR)
    local limb = StaticData.PARTS_STR[index] .. "_" .. side
    local amputationFullType = StaticData.AMPUTATION_CLOTHING_ITEM_BASE .. limb


    ItemsController.Zombie.SpawnAmputationItem(zombie, amputationFullType)


    -- Add reference and transmit it to server
    local pID = GetZombieID(zombie)
    local zombieKey = CommandsData.GetZombieKey()
    local zombiesMD = ModData.getOrCreate(zombieKey)
    if zombiesMD[pID] == nil then zombiesMD[pID] = {} end
    zombiesMD[pID][side] = amputationFullType
    ModData.add(zombieKey, zombiesMD)
    ModData.transmit(zombieKey)
end

-------------------------------

local bloodAmount = 10


---@param player IsoGameCharacter
---@param zombie IsoZombie
---@param handWeapon HandWeapon
local function HandleZombiesAmputations(player, zombie, handWeapon, damage)
    if not SandboxVars.TOC.EnableZombieAmputations then return end

    if not instanceof(zombie, "IsoZombie") or not instanceof(player, "IsoPlayer") then return end
    if player ~= getPlayer() then return end

    -- Check type of weapon. No hands, only knifes or such
    local weaponCategories = handWeapon:getScriptItem():getCategories()
    if not (weaponCategories:contains("Axe") or weaponCategories:contains("LongBlade")) then return end

    local isCrit = player:isCriticalHit()
    local randomChance = ZombRand(0, 100) > (100 - SandboxVars.TOC.ZombieAmputationDamageChance)
    if (damage > SandboxVars.TOC.ZombieAmputationDamageThreshold and randomChance) or isCrit then
        TOC_DEBUG.print("Amputating zombie limbs - damage: " .. tostring(damage))
        local zombieInv = zombie:getInventory()

        -- Check left or right
        local randSide = ZombRand(2)        -- Random side
        local preferredSide = randSide == 0 and "L" or "R"
        local alternateSide = preferredSide == "L" and "R" or "L"

        local predicatePreferred = preferredSide == "L" and PredicateAmputationItemLeft or PredicateAmputationItemRight
        local predicateAlternate = alternateSide == "L" and PredicateAmputationItemLeft or PredicateAmputationItemRight

        if not zombieInv:containsEval(predicatePreferred) then
            SpawnAmputation(zombie, preferredSide)
        elseif not zombieInv:containsEval(predicateAlternate) then
            SpawnAmputation(zombie, alternateSide)
        end

        TOC_DEBUG.print("Amputating zombie limbs - damage: " .. tostring(damage) .. ", preferred limb side: " .. preferredSide)

        -- add blood splat every couple of seconds for a while
        addBloodSplat(getCell():getGridSquare(zombie:getX(), zombie:getY(), zombie:getZ()), bloodAmount)
        local timerName = tostring(GetZombieID(zombie)) .. "_timer"
        timer:Create(timerName, 1, 10, function()
            addBloodSplat(getCell():getGridSquare(zombie:getX(), zombie:getY(), zombie:getZ()), bloodAmount)
        end)
    end
end

Events.OnWeaponHitCharacter.Add(HandleZombiesAmputations)

-----------------------------

local localOnlyZombiesMD

local function SetupZombiesModData()
    if not SandboxVars.TOC.EnableZombieAmputations then return end
    local zombieKey = CommandsData.GetZombieKey()
    localOnlyZombiesMD = ModData.getOrCreate(zombieKey)
end

Events.OnInitGlobalModData.Add(SetupZombiesModData)


---@param zombie IsoZombie
local function ReapplyAmputation(zombie)
    if not SandboxVars.TOC.EnableZombieAmputations then return end

    local pID = GetZombieID(zombie)

    if localOnlyZombiesMD[pID] ~= nil then
        -- check if zombie has amputation
        local zombiesAmpData = localOnlyZombiesMD[pID]
        local zombieInv = zombie:getInventory()
        local foundItem = zombieInv:containsEvalRecurse(PredicateAmputationItems)

        if foundItem then
            return
        else
            local leftAmp = zombiesAmpData['L']
            if leftAmp then
                ItemsController.Zombie.SpawnAmputationItem(zombie, leftAmp)
            end

            local rightAmp = zombiesAmpData['R']
            if rightAmp then
                ItemsController.Zombie.SpawnAmputationItem(zombie, rightAmp)
            end

            -- Removes reference, local only
            localOnlyZombiesMD[pID] = nil
        end
    end
end

Events.OnZombieUpdate.Add(ReapplyAmputation)
