local UEHelpers = require("UEHelpers")

local HOTKEY = Key.Y
local DEPOSIT_RANGE_METERS = 30.0
local DEPOSIT_RANGE_CM = DEPOSIT_RANGE_METERS * 100.0
local DEPOSIT_COOLDOWN_SECONDS = 0.25
local EXCLUDE_HOTBAR = true
local DEFAULT_HOTBAR_SLOT_COUNT = 8

local MOD_NAME = "QuickDepositToChests"
local lastDepositTime = 0

local function warn(message)
    print(string.format("[%s] %s\n", MOD_NAME, message))
end

local function getDistanceCm(actorA, actorB)
    if not actorA or not actorB or not actorA:IsValid() or not actorB:IsValid() then
        return nil
    end

    local ok, locA = pcall(function()
        return actorA:K2_GetActorLocation()
    end)
    local ok2, locB = pcall(function()
        return actorB:K2_GetActorLocation()
    end)

    if not ok or not ok2 or not locA or not locB then
        return nil
    end

    local dx = locA.X - locB.X
    local dy = locA.Y - locB.Y
    local dz = locA.Z - locB.Z
    return math.sqrt(dx * dx + dy * dy + dz * dz)
end

local function getOwnerActor(component)
    if not component or not component:IsValid() then
        return nil
    end

    local ok, owner = pcall(function()
        return component:GetOwner()
    end)

    if ok and owner and owner:IsValid() then
        return owner
    end

    return nil
end

local function isPlayerOwnedInventory(inventory, playerController, playerPawn)
    if not inventory or not inventory:IsValid() then
        return true
    end

    if playerController and playerController:IsValid() then
        local ok, playerInventory = pcall(function()
            return playerController:GetInventory()
        end)
        if ok and playerInventory and playerInventory:IsValid() and inventory == playerInventory then
            return true
        end

        ok, playerInventory = pcall(function()
            return playerController:GetPersonalInventory()
        end)
        if ok and playerInventory and playerInventory:IsValid() and inventory == playerInventory then
            return true
        end
    end

    local owner = getOwnerActor(inventory)
    if owner and owner:IsValid() and playerPawn and playerPawn:IsValid() and owner == playerPawn then
        return true
    end

    return false
end

local function supportsFillStacks(inventory)
    if not inventory or not inventory:IsValid() then
        return false
    end

    local ok, value = pcall(function()
        return inventory.bSupportsSortAndFillStacks
    end)

    if ok and value ~= nil then
        return value == true
    end

    return true
end

local function findNearbyChestInventories(playerController, playerPawn)
    local chestInventories = {}
    local ok, allInventories = pcall(function()
        return FindAllOf("InventoryComponent")
    end)

    if not ok or not allInventories then
        return chestInventories
    end

    for _, inventory in ipairs(allInventories) do
        if inventory and inventory:IsValid() then
            if not isPlayerOwnedInventory(inventory, playerController, playerPawn) and supportsFillStacks(inventory) then
                local owner = getOwnerActor(inventory)
                if owner and owner:IsValid() then
                    local distance = getDistanceCm(playerPawn, owner)
                    if distance and distance <= DEPOSIT_RANGE_CM then
                        table.insert(chestInventories, inventory)
                    end
                end
            end
        end
    end

    return chestInventories
end

local function getHotbarSlotCount(playerInventory)
    if not EXCLUDE_HOTBAR then
        return 0
    end

    local ok, count = pcall(function()
        return playerInventory.NumberOfQuickActionSlots
    end)
    if ok and type(count) == "number" and count >= 0 then
        return count
    end

    return DEFAULT_HOTBAR_SLOT_COUNT
end

local function getMaxSlotCount(playerInventory)
    local ok, count = pcall(function()
        return playerInventory.MaxSlotCount
    end)
    if ok and type(count) == "number" and count > 0 then
        return count
    end

    local okArr, slots = pcall(function()
        return playerInventory.ItemSlots
    end)
    if okArr and slots then
        local okNum, num = pcall(function()
            return slots:GetArrayNum()
        end)
        if okNum and type(num) == "number" then
            return num
        end
    end

    return 0
end

local function resolveItemData(item)
    if not item then
        return nil
    end

    local okValid, valid = pcall(function()
        return item:IsValid()
    end)
    if not okValid or not valid then
        return nil
    end

    local okData, data = pcall(function()
        return item.ItemData
    end)
    if okData and data then
        local okDataValid, dataValid = pcall(function()
            return data:IsValid()
        end)
        if okDataValid and dataValid then
            return data
        end
    end

    return item
end

local function chestHasItemType(chestInventory, itemData)
    if not itemData then
        return false
    end

    local ok, count = pcall(function()
        return chestInventory:GetNumItemsByData(itemData)
    end)
    if ok and type(count) == "number" then
        return count > 0
    end

    return false
end

local function getSlotItemCount(playerInventory, slotIndex)
    local ok, count = pcall(function()
        return playerInventory:GetNumItemsInSlot(slotIndex)
    end)
    if ok and type(count) == "number" then
        return count
    end
    return 0
end

local function quickDepositToNearbyChests()
    local now = os.clock()
    if now - lastDepositTime < DEPOSIT_COOLDOWN_SECONDS then
        return
    end
    lastDepositTime = now

    local playerController = UEHelpers.GetPlayerController()
    if not playerController or not playerController:IsValid() then
        warn("Player controller not found.")
        return
    end

    local playerPawn = playerController.Pawn
    if not playerPawn or not playerPawn:IsValid() then
        warn("Player pawn not found.")
        return
    end

    local ok, playerInventory = pcall(function()
        return playerController:GetInventory()
    end)
    if not ok or not playerInventory or not playerInventory:IsValid() then
        warn("Player inventory not found.")
        return
    end

    local inventoryController
    ok, inventoryController = pcall(function()
        return playerController:GetInventoryController()
    end)
    if not ok or not inventoryController or not inventoryController:IsValid() then
        warn("Inventory controller not found.")
        return
    end

    local chestInventories = findNearbyChestInventories(playerController, playerPawn)
    if #chestInventories == 0 then
        warn(string.format("No chests found within %.0f meters.", DEPOSIT_RANGE_METERS))
        return
    end

    local hotbarSlotCount = getHotbarSlotCount(playerInventory)
    local maxSlotCount = getMaxSlotCount(playerInventory)
    if maxSlotCount <= 0 then
        warn("Could not read inventory slot count.")
        return
    end

    local depositedSlots = 0
    for slotIndex = hotbarSlotCount, maxSlotCount - 1 do
        if getSlotItemCount(playerInventory, slotIndex) > 0 then
            local item
            local okItem, slotItem = pcall(function()
                return playerInventory:GetItemFromSlot(slotIndex)
            end)
            if okItem then
                item = slotItem
            end

            local itemData = resolveItemData(item)
            if itemData then
                for _, chestInventory in ipairs(chestInventories) do
                    if chestInventory and chestInventory:IsValid()
                        and chestHasItemType(chestInventory, itemData) then
                        local moveOk, moved = pcall(function()
                            return inventoryController:MoveItemBetweenInventoriesAnySlot(
                                playerInventory, slotIndex, chestInventory)
                        end)

                        if moveOk and moved then
                            depositedSlots = depositedSlots + 1
                        elseif not moveOk then
                            warn(string.format("Deposit failed for slot %d: %s", slotIndex, tostring(moved)))
                        end

                        if getSlotItemCount(playerInventory, slotIndex) <= 0 then
                            break
                        end
                    end
                end
            end
        end
    end

    if depositedSlots > 0 then
        warn(string.format("Quick-deposited matching items from %d slot(s) into nearby chest(s).", depositedSlots))
    else
        warn("No matching item stacks were deposited. Chests may not already contain those item types.")
    end
end

RegisterKeyBind(HOTKEY, {}, function()
    ExecuteInGameThread(function()
        local ok, err = pcall(quickDepositToNearbyChests)
        if not ok then
            warn(string.format("Unexpected error: %s", tostring(err)))
        end
    end)
end)

local hotkeyName = "Y"
for name, key in pairs(Key) do
    if key == HOTKEY then
        hotkeyName = name
        break
    end
end

print(string.format(
    "[%s] Loaded. Press %s to quick-deposit matching items (excluding the hotbar) into chests within %.0f meters.\n",
    MOD_NAME,
    hotkeyName,
    DEPOSIT_RANGE_METERS
))
