ForgeController = Controller:new()
ForgeButton = nil

local cloneValue = Helpers.cloneValue
local replaceTableContents = Helpers.replaceTableContents
local normalizeClassPriceEntries = Helpers.normalizeClassPriceEntries
local normalizeTierPriceEntries = Helpers.normalizeTierPriceEntries
-- Don't cache formatHistoryDate as a local since it may be updated
-- local formatHistoryDate = Helpers.formatHistoryDate

-- Store all callback references to prevent garbage collection
ForgeController.callbacks = {}

ForgeController.showResult = false
ForgeController.showBonus = false

local rightArrow = Helpers.rightArrow
local filledRightArrow = Helpers.filledRightArrow
ForgeController.result = cloneValue(Helpers.baseResult)
ForgeController.baseResult = Helpers.baseResult

function ForgeController:onGameStart()
    if g_game.getFeature(GameForgeConvergence) then -- Summer Update 2017
        self:updateResourceBalances()
        
        -- Store event callbacks to prevent garbage collection
        self.callbacks.onBrowseForgeHistory = onBrowseForgeHistory
        self.callbacks.forgeData = forgeData
        self.callbacks.onOpenForge = onOpenForge
        self.callbacks.forgeResultData = forgeResultData
        self.callbacks.onResourcesBalanceChange = function() self:updateResourceBalances() end
        
        self:registerEvents(g_game, {
            onBrowseForgeHistory = self.callbacks.onBrowseForgeHistory,
            forgeData = self.callbacks.forgeData,
            onOpenForge = self.callbacks.onOpenForge,
            onResourcesBalanceChange = self.callbacks.onResourcesBalanceChange,
            forgeResultData = self.callbacks.forgeResultData
        })

        if not ForgeButton then
            ForgeButton = modules.game_mainpanel.addToggleButton('ForgeButton', tr('Open Exaltation Forge'),
                '/images/options/button-exaltation-forge.png', function()
                    self:toggle()
                end)
        end
        self.currentTab = 'conversion'
    else
        -- Store unload callback
        self.callbacks.unloadModule = function()
            g_modules.getModule("game_forge"):unload()
        end
        scheduleEvent(self.callbacks.unloadModule, 100)
    end
end

local function resetInfo()
    ForgeController.transfer.exaltedCoresLabel = "???"
    ForgeController.transfer.selected = cloneValue(ForgeController.baseSelected)
    ForgeController.transfer.selectedTarget = cloneValue(ForgeController.baseSelected)
    ForgeController.rawPrice = 0
    ForgeController.formattedPrice = "???"
    ForgeController.transfer.canTransfer = false
    ForgeController.transfer.isConvergence = false
    ForgeController.transfer.title = "Transfer Requirements"
    ForgeController.transfer.dustLabel = ForgeController.transfer.dust
    replaceTableContents(ForgeController.transfer.currentList, ForgeController.transfer.items)
    ForgeController.fusion.title = "Further Items Needed For Fusion"
    ForgeController.fusion.selected = cloneValue(ForgeController.baseSelected)
    ForgeController.fusion.selectedTarget = cloneValue(ForgeController.baseSelected)
    ForgeController.fusion.isConvergence = false
    ForgeController.fusion.canTransfer = false
    ForgeController.fusion.canFusion = false
    ForgeController.fusion.chanceImprovedChecked = false
    ForgeController.fusion.reduceTierLossChecked = false
    ForgeController.fusion.dustLabel = ForgeController.fusion.dust
    replaceTableContents(ForgeController.fusion.currentList, ForgeController.fusion.items)
    ForgeController.showResult = false
    ForgeController.showBonus = false
    ForgeController.result = cloneValue(Helpers.baseResult)
    ForgeController.description = ""
    ForgeController.waitingForResult = false
    if ForgeController.resultTimeout then
        removeEvent(ForgeController.resultTimeout)
        ForgeController.resultTimeout = nil
    end
end

function ForgeController:handleDescription(currentType)
    Helpers.handleDescription(ForgeController, currentType)
end

function ForgeController:show(skipRequest)
    if not g_game.getFeature(GameForgeConvergence) then
        return ForgeController:hide()
    end
    resetInfo()
    local needsReload = not self.ui or self.ui:isDestroyed()
    if needsReload then
        self:loadHtml('game_forge.html')
    end

    if not self.ui then
        return
    end

    if not skipRequest then
        g_game.openPortableForgeRequest()
    end

    self.ui:centerIn('parent')
    self.ui:show()
    self.ui:raise()
    self.ui:focus()

    if ForgeButton then
        ForgeButton:setOn(true)
    end

    self:updateResourceBalances()
end

function ForgeController:hide()
    resetInfo()
    if not self.ui then
        return
    end

    self.ui:hide()

    if ForgeButton then
        ForgeButton:setOn(false)
    end
    if self.ui and not self.ui:isDestroyed() then
        self.ui:destroy()
    end
    self.ui = nil
end

function ForgeController:toggle()
    ForgeController:toggleConversionMenu()
    resetInfo()
    if ForgeController.rawData then
        forgeData(ForgeController.rawData)
    end
    if not self.ui or self.ui:isDestroyed() then
        self:show()
        return
    end

    if self.ui:isVisible() then
        self:hide()
    else
        self:show()
    end
end

ForgeController.currentDust = 0
ForgeController.currentSlivers = 0
ForgeController.currentExaltedCores = 0
ForgeController.rawCurrentGold = 0
ForgeController.currentGold = 0

ForgeController.currentTab = 'conversion'
function ForgeController:updateResourceBalances()
    if not self.ui then
        return
    end

    local player = g_game.getLocalPlayer()
    self.currentDust = player:getResourceBalance(ResourceTypes.FORGE_DUST or 70)
    self.currentSlivers = player:getResourceBalance(ResourceTypes.FORGE_SLIVER)
    self.currentExaltedCores = player:getResourceBalance(ResourceTypes.FORGE_CORES)
    self.rawCurrentGold = player:getTotalMoney() or 0
    self.currentGold = comma_value(self.rawCurrentGold)
end

ForgeController.baseSelected = {
    id = -1,
    tier = 0,
    clip = {},
    imagePath = nil,
    rarityClipObject = nil,
    count = 0,
    countLabel = "0 / 1",
    key = "",
    targetTier = 0
}

function ForgeController:onInit()
end

function ForgeController:terminate()
    -- Clean up any pending events
    if ForgeController.resultTimeout then
        removeEvent(ForgeController.resultTimeout)
        ForgeController.resultTimeout = nil
    end
    
    -- Clean up all stored callbacks
    if ForgeController.callbacks then
        for key, _ in pairs(ForgeController.callbacks) do
            ForgeController.callbacks[key] = nil
        end
        ForgeController.callbacks = {}
    end
    
    -- Reset flags
    ForgeController.waitingForResult = false
    ForgeController.showResult = false
    ForgeController.showBonus = false
    
    -- Hide UI
    if self.ui and not self.ui:isDestroyed() then
        self:hide()
    end
    
    -- Remove button
    if ForgeButton then
        ForgeButton:destroy()
        ForgeButton = nil
    end
end

function ForgeController:getPrice(prices, itemId, currentTier, isConvergence, isTransfer)
    local price = 0
    local tierIndex = (tonumber(currentTier) or 0) + 1

    if isTransfer and isConvergence then
        for tier, _price in pairs(prices) do
            if isConvergence then
                tier = tier > 1 and (tier - 1) or tier
            end
            if currentTier == tier then
                price = tonumber(_price) or 0
                break
            end
        end
    elseif isConvergence then
        for tier, _price in pairs(prices) do
            if tierIndex == tier then
                price = tonumber(_price) or 0
                break
            end
        end
    else
        local itemPtr = Item.create(itemId, 1)

        local classification = itemPtr and itemPtr.getClassification and itemPtr:getClassification() or 0
        if classification <= 0 then return 0 end

        for class, tiers in pairs(prices) do
            if class == classification then
                for tier, _price in pairs(tiers) do
                    if tier == tierIndex or (isTransfer and tier == currentTier) then
                        price = tonumber(_price) or 0
                        break
                    end
                end
            end
        end
    end

    -- Guard against negative prices from server data (e.g. uint64 overflow in pushInteger).
    -- A zero price will block forgeAction() via the rawPrice <= 0 check, which is intentional:
    -- the client should not allow forging when the server sends invalid pricing.
    if price < 0 then price = 0 end

    self.rawPrice = price
    self.formattedPrice = comma_value(price)
    return self.formattedPrice
end

local forgeActions = {
    FUSION = 0,
    TRANSFER = 1
}

-- Named callback functions to prevent garbage collection issues
ForgeController.callbacks.closeResultCallback = function()
    ForgeController.showResult = false
    ForgeController.showBonus = false

    ForgeController:hide()
    if ForgeController.rawOpenForgeData then
        onOpenForge(ForgeController.rawOpenForgeData)
    end
end

ForgeController.callbacks.updateBonusButton = function()
    ForgeController.result.button = "Close"
    ForgeController.result.bonusAction = ForgeController.callbacks.closeResultCallback
end

ForgeController.callbacks.showBonusCallback = function()
    ForgeController.showResult = false
    ForgeController.showBonus = true

    -- Use the stored callback
    scheduleEvent(ForgeController.callbacks.updateBonusButton, 150)
end

function ForgeController:closeResult()
    ForgeController.callbacks.closeResultCallback()
end

function ForgeController:forgeAction(isTransfer)
    local data = isTransfer and ForgeController.transfer or ForgeController.fusion
    local canDoAction = isTransfer and data.canTransfer or data.canFusion
    if data.selected.id == -1 then return end
    if data.selectedTarget.id == -1 then return end
    if not canDoAction then return end

    if ForgeController.rawPrice <= 0 then return end
    if ForgeController.rawCurrentGold < ForgeController.rawPrice then return end
    if ForgeController.currentDust < data.dust then return end

    if isTransfer then
        if ForgeController.currentExaltedCores < data.necessaryExaltedCores then return end
    end

    local actionType = isTransfer and forgeActions.TRANSFER or forgeActions.FUSION

    local chanceImproved = false
    local reduceTierLoss = false

    if not isTransfer then
        chanceImproved = data.chanceImprovedChecked
        reduceTierLoss = data.reduceTierLossChecked
    end

    -- Set a flag to indicate we're waiting for a result
    ForgeController.waitingForResult = true
    
    -- Set a timeout to show an error if no result is received
    if ForgeController.resultTimeout then
        removeEvent(ForgeController.resultTimeout)
    end
    
    -- Store the callback function to maintain a reference
    ForgeController.callbacks.timeoutCallback = function()
        if ForgeController.waitingForResult then
            ForgeController.waitingForResult = false
            -- If no result received after 5 seconds, refresh the forge window
            if ForgeController.rawOpenForgeData then
                onOpenForge(ForgeController.rawOpenForgeData)
            end
        end
    end
    
    ForgeController.resultTimeout = scheduleEvent(ForgeController.callbacks.timeoutCallback, 5000)

    g_game.forgeRequest(actionType, data.isConvergence, data.selected.id, data.selected.tier,
        data.selectedTarget.id, chanceImproved, reduceTierLoss)
end

function ForgeController:resultSystemEvent()
    ForgeController.result.buttonLabel = "Close"

    if not ForgeController.ui or ForgeController.ui:isDestroyed() then
        return
    end

    if ForgeController.result.eventCount == 1 then
        ForgeController.result.arrows = {
        { arrow = Helpers.filledRightArrow, },
            { arrow = Helpers.rightArrow, },
            { arrow = Helpers.rightArrow, }

        }
    elseif ForgeController.result.eventCount == 2 then
        ForgeController.result.arrows = {
            { arrow = Helpers.filledRightArrow, },
            { arrow = Helpers.filledRightArrow, },
            { arrow = Helpers.rightArrow, }
        }
    elseif ForgeController.result.eventCount == 3 then
        ForgeController.result.arrows = {
            { arrow = Helpers.filledRightArrow, },
            { arrow = Helpers.filledRightArrow, },
            { arrow = Helpers.filledRightArrow, }

        }
    elseif ForgeController.result.eventCount == 4 then
        ForgeController.result.arrows = {
            { arrow = Helpers.rightArrow, },
            { arrow = Helpers.filledRightArrow, },
            { arrow = Helpers.filledRightArrow, }
        }
    elseif ForgeController.result.eventCount == 5 then
        ForgeController.result.arrows = {
            { arrow = Helpers.rightArrow, },
            { arrow = Helpers.rightArrow, },
            { arrow = Helpers.filledRightArrow, }
        }
    elseif ForgeController.result.eventCount == 6 then
        ForgeController.result.arrows = {
            { arrow = Helpers.rightArrow, },
            { arrow = Helpers.rightArrow, },
            { arrow = Helpers.rightArrow, }
        }
        if (ForgeController.result.bonus or 0) > 0 then
            ForgeController.result.buttonLabel = "Next"
        end

        if ForgeController.result.success then
            ForgeController.result.rightShader = "Outfit - ForgeSuccess"
            ForgeController.result.label = "Your transfer attempt was "
            ForgeController.result.labelResult = "successful."
            ForgeController.result.color = Helpers.green

            ForgeController.result.rightShader = ""
            ForgeController.result.leftItemId = -1
            ForgeController.result.leftTier = 0
        else
            ForgeController.result.label = "Your transfer attempt was "
            ForgeController.result.labelResult = "failed."
            ForgeController.result.rightShader = "Outfit - ForgeFailed"
            ForgeController.result.leftShader = ""
            ForgeController.result.color = Helpers.red

            -- Store the callback to maintain reference
            ForgeController.callbacks.clearRightItem = function()
                ForgeController.result.rightItemId = -1
                ForgeController.result.rightTier = 0
            end
            scheduleEvent(ForgeController.callbacks.clearRightItem, 1000)
        end
        return
    end

    ForgeController.result.eventCount = (ForgeController.result.eventCount or 0) + 1
    -- Store the callback to maintain reference
    ForgeController.callbacks.continueAnimation = function()
        ForgeController:resultSystemEvent()
    end
    scheduleEvent(ForgeController.callbacks.continueAnimation, 750)
end

function forgeResultData(rawData)
    -- Clear the waiting flag and timeout
    ForgeController.waitingForResult = false
    if ForgeController.resultTimeout then
        removeEvent(ForgeController.resultTimeout)
        ForgeController.resultTimeout = nil
    end
    
    -- Ensure UI is loaded before showing results
    if not ForgeController.ui or ForgeController.ui:isDestroyed() then
        ForgeController:show(true)
    end
    
    if not ForgeController.ui then
        -- UI failed to load, abort
        return
    end
    
    ForgeController.result = cloneValue(Helpers.baseResult)
    ForgeController.showResult = true
    ForgeController.showBonus = false
    local data = cloneValue(rawData)
    if type(data) == 'table' then
        for key, value in pairs(data) do
            ForgeController.result[key] = value
        end
    end

    if ForgeController.result.leftTier > 0 then
        ForgeController.result.leftClip = ItemsDatabase.getTierClip(ForgeController.result.leftTier)
    end

    if ForgeController.result.rightTier > 0 then
        ForgeController.result.rightClip = ItemsDatabase.getTierClip(ForgeController.result.rightTier)
    end

    ForgeController.result.arrows = {
        { arrow = Helpers.filledRightArrow, },
        { arrow = Helpers.rightArrow, },
        { arrow = Helpers.rightArrow, }
    }
    ForgeController.result.buttonLabel = "Close"
    ForgeController.result.bonusLabel = "-"


    ForgeController.result.label = ""
    if ForgeController.fusion.selected and ForgeController.fusion.selected.id ~= -1 then
        if ForgeController.fusion.isConvergence then
            ForgeController.result.title = "Convergence Fusion Result"
        else
            ForgeController.result.title = "Fusion Result"
        end
    end
    if ForgeController.transfer.selected and ForgeController.transfer.selected.id ~= -1 then
        if ForgeController.transfer.isConvergence then
            ForgeController.result.title = "Convergence Transfer Result"
        else
            ForgeController.result.title = "Transfer Result"
        end
    end
    if ForgeController.result.bonus == 0 then
        ForgeController.result.bonusAction = ForgeController.callbacks.closeResultCallback
    else
        ForgeController.result.buttonLabel = "Next"

        local isConvergence = ForgeController.transfer.selected == -1 and ForgeController.fusion.isConvergence
        if ForgeController.result.bonus == 1 then
            ForgeController.result.bonusItem = 37160
            local dust = isConvergence and ForgeController.fusion.convergenceDust or ForgeController.transfer.dust
            ForgeController.result.bonusLabel = string.format("Near! The used %s where not consumed.", dust)
        elseif ForgeController.result.bonus == 2 then
            if ForgeController.fusion.chanceImprovedChecked or ForgeController.fusion.reduceTierLossChecked then
                ForgeController.result.bonusItem = 37110
                ForgeController.result.bonusLabel = string.format("Fantastic! The used %s where not consumed.",
                    ForgeController.result.coreCount)
            end
        elseif ForgeController.result.bonus == 3 then
            ForgeController.result.bonusItem = 3031
            ForgeController.result.bonusLabel = string.format("Awesome! The used %s where not consumed.",
                comma_value(ForgeController.rawPrice))
        elseif ForgeController.result.bonus == 4 then
            ForgeController.result.bonusItem = ForgeController.result.leftItemId
            if ForgeController.result.rightTier >= 2 then
                ForgeController.result.bonusTier = ForgeController.result.rightTier - 1
                ForgeController.result.bonusItemClip = ItemsDatabase.getTierClip(ForgeController.result.bonusTier)
            end
            ForgeController.result.bonusLabel = "What luck! Your item only lost one tier instead of being\nconsumed."
        end

        ForgeController.result.bonusAction = ForgeController.callbacks.showBonusCallback
    end

    ForgeController.result.leftShader = "Outfit - ForgeDonor"
    ForgeController.result.rightShader = "Outfit - cyclopedia-black"
    ForgeController.result.eventCount = 1
    ForgeController.callbacks.startResultAnimation = function()
        ForgeController:resultSystemEvent()
    end
    scheduleEvent(ForgeController.callbacks.startResultAnimation, 750)
end

local buttonClip = { x = 0, y = 0, width = 43, height = 20 }
local buttonClipPressed = { x = 0, y = 20, width = 43, height = 20 }
-- FUSION MENU
ForgeController.fusion = {
    prices = {},
    convergencePrices = {},
    grades = {},
    dust = 100,
    convergenceDust = 130,
    reduceTierLoss = 50,
    reduceTierLossLabel = "100%",
    reduceTierLossButtonClip = buttonClip,
    reduceTierLossChecked = false,
    reduceTierLossBase = 100,
    chanceImprovedLabel = "50%",
    chanceImprovedButtonClip = buttonClip,
    chanceImprovedChecked = false,
    chanceImproved = 15,
    chanceBase = 50,
    dustLabel = 100,
    hasConvergence = false,
    isConvergence = false,
    title = "Further Items Needed For Fusion",

    clip = { x = 0, y = 0, width = 116, height = 34 },
    items = {},
    selected = cloneValue(ForgeController.baseSelected),
    selectedTarget = cloneValue(ForgeController.baseSelected),
    canFusion = false,
    currentList = {},
}

-- Store callback in ForgeController to prevent garbage collection
ForgeController.fusion.handleSelect = function(item)
    ForgeController.fusion.selectedTarget = cloneValue(ForgeController.baseSelected)
    ForgeController:handleSelect(ForgeController.fusion, item, false)
end

-- Store callback in ForgeController to prevent garbage collection
ForgeController.fusion.exaltedCoreImprovements = function(currentType)
    if currentType == 'improve-chance' then
        ForgeController.fusion.chanceImprovedChecked = not ForgeController.fusion.chanceImprovedChecked
        if ForgeController.fusion.chanceImprovedChecked then
            ForgeController.fusion.chanceImprovedButtonClip = buttonClipPressed
            ForgeController.fusion.chanceImprovedLabel = tostring(ForgeController.fusion.chanceBase +
                ForgeController.fusion.chanceImproved) .. "%"
        else
            ForgeController.fusion.chanceImprovedButtonClip = buttonClip
            ForgeController.fusion.chanceImprovedLabel = tostring(ForgeController.fusion.chanceBase) .. "%"
        end
    elseif currentType == 'reduce-loss' then
        ForgeController.fusion.reduceTierLossChecked = not ForgeController.fusion.reduceTierLossChecked
        if ForgeController.fusion.reduceTierLossChecked then
            ForgeController.fusion.reduceTierLossButtonClip = buttonClipPressed
            ForgeController.fusion.reduceTierLossLabel = tostring(ForgeController.fusion.reduceTierLoss) .. "%"
        else
            ForgeController.fusion.reduceTierLossButtonClip = buttonClip
            ForgeController.fusion.reduceTierLossLabel = tostring(ForgeController.fusion.reduceTierLossBase) .. "%"
        end
    end
end

function ForgeController:toggleFusionMenu()
    self:resetTabsClip()
    self.currentTab = 'fusion'
    self.fusion.clip = { x = 0, y = 34, width = 116, height = 34 }
end

-- TRANSFER MENU
ForgeController.transfer = {
    prices = {},
    convergencePrices = {},
    grades = {},
    dust = 100,
    convergenceDust = 160,
    hasConvergence = false,
    title = "Transfer Requirements",
    dustLabel = 100,
    exaltedCoresLabel = "???",
    necessaryExaltedCores = 0,
    clip = { x = 0, y = 0, width = 116, height = 34 },
    isConvergence = false,
    items = {
        donors = {},
        receivers = {}
    },
    convergenceItems = {
        donors = {},
        receivers = {}
    },
    currentList = {
        donors = {},
        receivers = {}
    },
    selected = cloneValue(ForgeController.baseSelected),
    selectedTarget = cloneValue(ForgeController.baseSelected),
    canTransfer = false
}
function ForgeController:toggleTransferMenu()
    self:resetTabsClip()
    self.currentTab = 'transfer'
    self.transfer.clip = { x = 0, y = 34, width = 116, height = 34 }
end

local function handleFusionItems(data)
    local currentList = {}

    for i, item in pairs(data) do
        item.key = ("%d_%d"):format(item.id, item.tier)
        if item.tier > 0 then
            item.clip = ItemsDatabase.getTierClip(item.tier)
        end

        local _, imagePath, rarityClipObject = ItemsDatabase.getClipAndImagePath(item.id)
        if imagePath then
            item.imagePath = imagePath
            item.rarityClipObject = rarityClipObject
        end

        item.countLabel = string.format("%d / %d", item.count, 1)
        table.insert(currentList, item)
    end

    table.sort(currentList, function(a, b)
        if a.tier == b.tier then
            return a.id < b.id
        end
        return a.tier > b.tier
    end)

    return currentList
end

function ForgeController:handleSelect(data, item, isTransfer)
    data.selected = cloneValue(ForgeController.baseSelected)
    data.selectedTarget = cloneValue(ForgeController.baseSelected)
    data.canFusion = false
    data.canTransfer = false
    local isConvergence = data.isConvergence

    if isTransfer then
        data.selected = item
        local targetTier = isConvergence and item.tier or (item.tier - 1)
        for _, values in pairs(data.grades) do
            if values.tier == targetTier then
                data.exaltedCoresLabel = values.exaltedCores
                data.necessaryExaltedCores = values.exaltedCores
                break
            end
        end

        local list = isConvergence and data.convergenceItems or data.items
        local receivers = {}
        for _, _item in pairs(list.receivers) do
            if item.id ~= _item.id then
                table.insert(receivers, _item)
            end
        end

        data.currentList.receivers = receivers
    else
        data.selected = cloneValue(item)
        data.selected.targetTier = data.selected.tier + 1
        data.selected.targetClip = ItemsDatabase.getTierClip(data.selected.targetTier)
        local _, imagePath, rarityClipObject = ItemsDatabase.getClipAndImagePath(data.selected.id)
        if imagePath then
            data.selected.imagePath = imagePath
            data.selected.rarityClipObject = rarityClipObject
        end

        if not isConvergence then
            data.selectedTarget = cloneValue(item)
            data.selectedTarget.targetTier = data.selectedTarget.tier + 1
            data.selectedTarget.targetClip = ItemsDatabase.getTierClip(data.selectedTarget.targetTier)
            local _, imagePath, rarityClipObject = ItemsDatabase.getClipAndImagePath(data.selectedTarget.id)
            if imagePath then
                data.selectedTarget.imagePath = imagePath
                data.selectedTarget.rarityClipObject = rarityClipObject
            end
        end
    end

    local prices = isConvergence and data.convergencePrices or data.prices
    ForgeController:getPrice(prices, item.id, data.selected.tier, isConvergence, isTransfer)

    if ForgeController.rawPrice > 0 and ForgeController.rawCurrentGold >= ForgeController.rawPrice then
        if ForgeController.currentDust >= ForgeController.fusion.dust then
            if isConvergence then return end
            ForgeController.fusion.canFusion = true
        end
    end
end

-- Store callback in ForgeController to prevent garbage collection
ForgeController.transfer.handleSelect = function(item)
    ForgeController.transfer.exaltedCoresLabel = "???"
    if not item then
        return
    end
    ForgeController:handleSelect(ForgeController.transfer, item, true)
end

function ForgeController:handleSelectTarget(item, isTransfer)
    local data = isTransfer and ForgeController.transfer or ForgeController.fusion
    data.selectedTarget = cloneValue(ForgeController.baseSelected)

    ForgeController.transfer.canTransfer = false
    ForgeController.fusion.canFusion = false
    if not item then
        return
    end

    data.selectedTarget = cloneValue(item)
    local targetTier = 0
    if isTransfer then
        targetTier = data.isConvergence and data.selected.tier or (data.selected.tier - 1)
    else
        targetTier = data.selected.tier + 1
    end

    data.selectedTarget.targetTier = targetTier
    data.selectedTarget.clip = ItemsDatabase.getTierClip(data.selectedTarget.targetTier)
    local _, imagePath, rarityClipObject = ItemsDatabase.getClipAndImagePath(item.id)
    if imagePath then
        data.selectedTarget.imagePath = imagePath
        data.selectedTarget.rarityClipObject = rarityClipObject
    end

    if ForgeController.currentDust < data.dust then return end

    if ForgeController.rawPrice > 0 and ForgeController.rawCurrentGold >= ForgeController.rawPrice then
        if isTransfer then
            if ForgeController.currentExaltedCores < ForgeController.transfer.necessaryExaltedCores then
                return
            end
            data.canTransfer = true
        else
            -- For convergence fusion, we need at least 2 items of the same type
            if data.isConvergence then
                if item.count >= 2 then
                    data.canFusion = true
                end
            else
                data.canFusion = true
            end
        end
    end
end

function ForgeController:toggleConvergence(isTransfer)
    local data = isTransfer and ForgeController.transfer or ForgeController.fusion
    data.isConvergence = not data.isConvergence
    data.selected = cloneValue(ForgeController.baseSelected)
    data.selectedTarget = cloneValue(ForgeController.baseSelected)
    ForgeController.formattedPrice = "???"
    ForgeController.rawPrice = 0

    local source = data.isConvergence and data.convergenceItems or data.items

    if isTransfer then
        data.canTransfer = false
        if not data.isConvergence then
            data.title = "Transfer Requirements"
            data.dustLabel = data.dust
        else
            data.title = "Convergence Transfer Requirements"
            data.dustLabel = data.convergenceDust
        end
    else
        data.canFusion = false
        if not data.isConvergence then
            data.title = "Further Items Needed For Fusion"
            data.dustLabel = data.dust
        else
            data.dustLabel = data.convergenceDust
            data.title = "Further Items Needed For Convergence Fusion"
        end
    end

    -- Replace contents in-place to preserve the table reference for reactive UI
    replaceTableContents(data.currentList, source)
end

-- TRANSFER MENU

local function handleTransferItems(data)
    local currentList = {
        donors = {},
        receivers = {}
    }
    for slot, currentType in pairs(data) do
        for transferType in pairs(currentType) do
            for __, item in pairs(currentType[transferType]) do
                item.key = string.format("%d_%d", item.id, item.tier)
                item.slot = slot
                if item.tier > 0 then
                    item.clip = ItemsDatabase.getTierClip(item.tier)
                end
                local _, imagePath, rarityClipObject = ItemsDatabase.getClipAndImagePath(item.id)
                if imagePath then
                    item.imagePath = imagePath
                    item.rarityClipObject = rarityClipObject
                end
                item.countLabel = string.format("%d / %d", item.count, 1)
                table.insert(currentList[transferType], item)
            end
        end
    end

    table.sort(currentList.donors, function(a, b)
        if a.tier == b.tier then
            return a.id < b.id
        end
        return a.tier > b.tier
    end)


    return currentList
end

local function handleParseConvergenceFusionItems(data)
    local parsedItemsBySlot = {}
    for slot, sloItems in pairs(data) do
        for _, item in pairs(sloItems) do
            item.key = ("%d_%d"):format(item.id, item.tier)
            item.slot = slot
            if item.tier > 0 then
                item.clip = ItemsDatabase.getTierClip(item.tier)
            end

            local _, imagePath, rarityClipObject = ItemsDatabase.getClipAndImagePath(item.id)
            if imagePath then
                item.imagePath = imagePath
                item.rarityClipObject = rarityClipObject
            end

            table.insert(parsedItemsBySlot, item)
        end
    end

    return data, parsedItemsBySlot
end

function onOpenForge(data)
    ForgeController.rawOpenForgeData = data
    
    -- Don't update if we're showing the result animation or waiting for result
    if ForgeController.showResult or ForgeController.showBonus or ForgeController.waitingForResult then
        return
    end
    
    ForgeController.fusion.chanceImprovedChecked = false
    ForgeController.fusion.reduceTierLossChecked = false
    ForgeController.conversion.dustMax = data.dustLevel or ForgeController.conversion.dustMax or 100

    -- FUSION ITEMS
    local items = cloneValue(data.fusionItems or {})
    local fusionItems = handleFusionItems(items)
    ForgeController.fusion.items = fusionItems
    replaceTableContents(ForgeController.fusion.currentList, fusionItems)
    local convergenceFusion = cloneValue(data.convergenceFusion or {})
    local _, convergenceItemsBySlot = handleParseConvergenceFusionItems(convergenceFusion)
    ForgeController.fusion.convergenceItems = convergenceItemsBySlot
    -- FUSION ITEMS

    -- TRANSFER ITEMS
    local transfers = cloneValue(data.transfers or {})
    local transferItems = handleTransferItems(transfers)
    ForgeController.transfer.items = transferItems
    replaceTableContents(ForgeController.transfer.currentList, transferItems)

    local convergenceTransfers = cloneValue(data.convergenceTransfers or {})
    local convergenceTransferItems = handleTransferItems(convergenceTransfers)
    ForgeController.transfer.convergenceItems = convergenceTransferItems
    -- TRANSFER ITEMS

    local shouldShow = not ForgeController.ui or ForgeController.ui:isDestroyed() or not ForgeController.ui:isVisible()
    if shouldShow then
        ForgeController:show(true)
        ForgeController:toggleFusionMenu()
    end
end

function forgeData(data)
    ForgeController.rawData = data
    ForgeController:updateResourceBalances()
    -- FUSION
    local classPrices = normalizeClassPriceEntries(data.classPrices)
    if next(classPrices) then
        ForgeController.fusion.prices = classPrices
    end

    local convergenceFusion = normalizeTierPriceEntries(data.convergenceFusionPrices)
    if next(convergenceFusion) then
        ForgeController.fusion.convergencePrices = convergenceFusion
    end

    ForgeController.fusion.grades = data.fusionGrades or {}
    ForgeController.fusion.convergenceDust = data.convergenceFusionDust or 130
    ForgeController.fusion.dust = data.normalDustFusion or 100
    ForgeController.fusion.reduceTierLoss = data.fusionReduceTierLoss or 50
    ForgeController.fusion.chanceImproved = data.fusionChanceImproved or 15
    ForgeController.fusion.chanceBase = data.fusionChanceBase or 50
    ForgeController.fusion.hasConvergence = data.hasConvergence or false
    ForgeController.fusion.dustLabel = ForgeController.fusion.dust
    -- FUSION

    -- TRANSFER
    ForgeController.transfer.prices = ForgeController.fusion.prices or {}
    local convergenceTransfer = normalizeTierPriceEntries(data.convergenceTransferPrices)
    if next(convergenceTransfer) then
        ForgeController.transfer.convergencePrices = convergenceTransfer
    end
    ForgeController.transfer.convergenceDust = data.convergenceDustTransfer or 160
    ForgeController.transfer.dust = data.normalDustTransfer or 100
    ForgeController.transfer.hasConvergence = data.hasConvergence or false
    ForgeController.transfer.dustLabel = ForgeController.transfer.dust
    ForgeController.transfer.grades = data.fusionGrades or {}
    -- TRANSFER

    -- CONVERSION
    ForgeController.conversion.necessaryDustToSliver = 60
    ForgeController.conversion.dustToSilver = data.dustToSilver or 3
    ForgeController.conversion.dustToSilverLabel = string.format("Generate %d", ForgeController.conversion.dustToSilver)
    ForgeController.conversion.sliverToCore = data.sliverToCore or 50
    ForgeController.conversion.sliverToCoreLabel = "Generate 1"
    ForgeController.conversion.dustCap = data.maxDustCap or 225
    local dustMax = data.maxDustLevel or 100
    ForgeController.conversion.dustMax = dustMax
    ForgeController.conversion.dustMaxIncreaseCost = dustMax - 75
    ForgeController.conversion:handleButtons()
    -- CONVERSION
end

function ForgeController:getColor(currentType)
    ForgeController.conversion:handleButtons()
    local red = Helpers.red
    local base = Helpers.grey
    local green = Helpers.green

    if currentType == "dustToSilver" then
        if self.currentDust >= self.conversion.necessaryDustToSliver then
            return base
        else
            return red
        end
    end

    if currentType == "sliverToCore" then
        if self.currentSlivers >= self.conversion.sliverToCore then
            return base
        else
            return red
        end
    end

    if currentType == "increaseDustLimit" then
        if self.currentDust >= self.conversion.dustMaxIncreaseCost then
            return base
        else
            return red
        end
    end

    if currentType == "fusion-preview-count-label" then
        if self.fusion.selected.id == -1 then
            return red
        end
    end

    if currentType == "transfer-preview-count-label" then
        if self.transfer.selected.id == -1 then
            return red
        end
    end

    if currentType == "fusion-dust" then
        local necessaryDust = self.fusion.isConvergence and self.fusion.convergenceDust or self.fusion.dust
        if self.fusion.dustLabel ~= necessaryDust then
            self.fusion.dustLabel = necessaryDust
        end

        if self.currentDust < necessaryDust then
            return red
        end
    end

    if currentType == "transfer-dust" or currentType == "transfer-exalted" then
        if self.transfer.selected.id == -1 then
            return red
        end

        if currentType == "transfer-dust" then
            local necessaryDust = self.transfer.isConvergence and self.transfer.convergenceDust or self.transfer.dust
            if self.transfer.dustLabel ~= necessaryDust then
                self.transfer.dustLabel = necessaryDust
            end
            if self.currentDust < necessaryDust then
                return red
            end
        end
        if currentType == "transfer-exalted" then
            if self.currentExaltedCores < self.transfer.necessaryExaltedCores then
                return red
            end
        end
    end

    if currentType == "price" then
        if self.rawPrice == 0 or self.rawPrice > self.rawCurrentGold then
            return red
        end
    end

    if currentType == "improve-chance" then
        if not self.fusion.chanceImprovedChecked then
            return red
        else
            return green
        end
    end

    if currentType == "reduce-loss" then
        if not self.fusion.reduceTierLossChecked then
            return red
        else
            return green
        end
    end

    if currentType == "improve-chance-cost" then
        if self.currentExaltedCores <= 0 then return red end

        if self.fusion.reduceTierLossChecked and self.currentExaltedCores == 1 then return red end
    end

    if currentType == "reduce-loss-cost" then
        if self.currentExaltedCores <= 0 then return red end

        if self.fusion.chanceImprovedChecked and self.currentExaltedCores == 1 then return red end
    end

    return base
end

-- CONVERSION MENU
ForgeController.conversion = {
    fusionPrices = {},
    dustCap = 225,
    dustMax = 100,
    necessaryDustToSliver = 60,
    dustToSliver = 3,
    sliverToCore = 50,
    dustMaxIncreaseCost = 100,
    dustToSilverLabel = "Generate 3",
    sliverToCoreLabel = "Generate 1",
    dustMaxLabel = "Raise limit from\n100 to 101",
    disableDustConversion = true,
    disableSliverConversion = true,
    disableIncreaseDustLimit = true,
    clip = { x = 0, y = 0, width = 118, height = 34 },
    types = {
        DUST2SLIVER = 2,
        SLIVER2CORE = 3,
        INCREASE_DUST_LIMIT = 4
    },
}

function ForgeController.conversion.handleButtons()
    local dustMax = ForgeController.conversion.dustMax or 100
    ForgeController.conversion.disableDustConversion = ForgeController.currentDust <
        ForgeController.conversion.necessaryDustToSliver
    ForgeController.conversion.disableSliverConversion = ForgeController.currentSlivers <
        ForgeController.conversion.sliverToCore
    ForgeController.conversion.disableIncreaseDustLimit = ForgeController.currentDust <
        ForgeController.conversion.dustMaxIncreaseCost
    ForgeController.currentDustAndMaxLabel = string.format("%d/%d", ForgeController.currentDust,
        dustMax)
    if dustMax >= ForgeController.conversion.dustCap then
        ForgeController.conversion.dustMaxLabel = "Maximum Reached"
        ForgeController.conversion.disableIncreaseDustLimit = true
    else
        ForgeController.conversion.dustMaxLabel = string.format("Raise limit from\n%d to %d", dustMax, dustMax + 1)
    end
end

function ForgeController.conversion.toggle(conversionType)
    local player = g_game.getLocalPlayer()
    if not player then
        return
    end

    if conversionType == ForgeController.conversion.types.DUST2SLIVER then
        local dustBalance = player:getResourceBalance(ResourceTypes.FORGE_DUST) or 0
        if dustBalance <= ForgeController.conversion.necessaryDustToSliver then
            return
        end
        g_game.forgeRequest(conversionType)
        return
    end

    if conversionType == ForgeController.conversion.types.SLIVER2CORE then
        local sliverBalance = player:getResourceBalance(ResourceTypes.FORGE_SLIVER) or 0
        if sliverBalance <= ForgeController.conversion.sliverToCore then
            return
        end
        g_game.forgeRequest(conversionType)
        return
    end

    if conversionType == ForgeController.conversion.types.INCREASE_DUST_LIMIT then
        local dustBalance = player:getResourceBalance(ResourceTypes.FORGE_DUST) or 0
        local currentNecessaryDust = ForgeController.conversion.dustMaxIncreaseCost
        local maxDustCap = ForgeController.conversion.dustCap
        local maxDustLevel = ForgeController.conversion.dustMax

        if maxDustCap > 0 and maxDustLevel >= maxDustCap then
            return
        end

        if dustBalance < currentNecessaryDust then
            return
        end
        g_game.forgeRequest(conversionType)
    end
end

function ForgeController:resetTabsClip()
    self.fusion.clip = { x = 0, y = 0, width = 116, height = 34 }
    self.transfer.clip = { x = 0, y = 0, width = 116, height = 34 }
    self.conversion.clip = { x = 0, y = 0, width = 116, height = 34 }
    self.history.clip = { x = 0, y = 0, width = 118, height = 34 }
end

function ForgeController:toggleConversionMenu()
    self:resetTabsClip()
    self.currentTab = 'conversion'
    self.conversion.clip = { x = 0, y = 34, width = 116, height = 34 }
end

-- CONVERSION MENU

-- HISTORY MENU
ForgeController.history = {
    currentPage = 0,
    lastPage = 0,
    showPreviousButton = false,
    showNextButton = false,
    list = {},
    clip = { x = 0, y = 0, width = 118, height = 34 }
}
function ForgeController:toggleHistoryMenu()
    self:resetTabsClip()
    self.currentTab = 'history'
    g_game.sendForgeBrowseHistoryRequest(0)
    self.history.clip = { x = 0, y = 34, width = 118, height = 34 }
end

local historyActionLabels = {
    [0] = 'Fusion',
    [1] = 'Transfer',
    [2] = 'Conversion',
    [3] = 'Conversion',
    [4] = 'Conversion'
}

function onBrowseForgeHistory(page, lastPage, currentCount, historyList)
    page = math.max(tonumber(page) or 0, 1)
    lastPage = math.max(tonumber(lastPage) or page, 1)
    currentCount = tonumber(currentCount) or 0
    lastPage = lastPage > 1 and lastPage - 1 or lastPage


    ForgeController.history.currentPage = page
    ForgeController.history.lastPage = lastPage

    ForgeController.history.showPreviousButton = page > 1
    ForgeController.history.showNextButton = page < lastPage

    for _, entry in ipairs(historyList) do
        entry.createdAt = Helpers.formatHistoryDate(entry.createdAt)
        -- Convert actionType to number if it's a string, then look up the label
        local actionTypeNum = tonumber(entry.actionType) or entry.actionType
        entry.actionType = historyActionLabels[actionTypeNum] or 'Unknown'
        entry.description = entry.description or ''
        if entry.actionType == historyActionLabels[0] or entry.actionType == historyActionLabels[1] then
            local firstSpaceIndex = string.find(entry.description, " ")
            if firstSpaceIndex then
                entry.description = string.sub(entry.description, 1, firstSpaceIndex - 1)
            end
        end
    end
    ForgeController.history.list = historyList or {}
end

function ForgeController.history.onHistoryPreviousPage()
    local currentPage = ForgeController.history.currentPage or 0
    if currentPage <= 1 then
        return
    end

    g_game.sendForgeBrowseHistoryRequest(currentPage - 1)
end

function ForgeController.history.onHistoryNextPage()
    local currentPage = ForgeController.history.currentPage or 0
    local lastPage = ForgeController.history.lastPage or currentPage

    if currentPage >= lastPage then
        return
    end

    g_game.sendForgeBrowseHistoryRequest(currentPage + 1)
end
