---@class GW2
local GW = select(2, ...)

local TIME_FOR_3 = 0.6
local TIME_FOR_2 = 0.8

GwObjectivesScenarioContainerWidgetMixin = {}
GwObjectivesScenarioContainerMixin = {}
GwQuesttrackerScenarioBlockMixin = {}

local function HasValidTimerData(widgetInfo)
    return type(widgetInfo) == "table"
        and type(widgetInfo.timerMin) == "number"
        and type(widgetInfo.timerMax) == "number"
        and type(widgetInfo.timerValue) == "number"
        and widgetInfo.timerMax > widgetInfo.timerMin
        and widgetInfo.timerValue > widgetInfo.timerMin
        and widgetInfo.timerValue <= widgetInfo.timerMax
end

local function IsRealTimerWidget(widgetInfo)
    return widgetInfo and widgetInfo.hasTimer and HasValidTimerData(widgetInfo)
end

local function IsFakeTimerWidget(widgetInfo)
    return widgetInfo and not widgetInfo.hasTimer and HasValidTimerData(widgetInfo)
end

function GwObjectivesScenarioContainerWidgetMixin:RemoveAllWidgets()
    self.widgetFrames = {}
    self.timerWidgets = {}
    self.numTimers = 0

    if self.timerBlock.ticker then
        self.timerBlock.ticker:Cancel()
        self.timerBlock.ticker = nil
    end

    self.widgetPools:ReleaseAll()
end

function GwObjectivesScenarioContainerWidgetMixin:RegisterTimerWidget(widgetID, widgetFrame)
    if not self.timerWidgets[widgetID] then
        -- New timer added
        self.timerWidgets[widgetID] = widgetFrame
        if not self.timerBlock.ticker then
            self.timerBlock.ticker = C_Timer.NewTicker(1,
                function()
                    for id, widget in pairs(self.timerWidgets) do
                        self:ProcessWidget(id, widget.widgetType)
                    end
                end)
        end

        self.numTimers = self.numTimers + 1
    end
end

function GwObjectivesScenarioContainerWidgetMixin:UnregisterTimerWidget(widgetID)
    if self.timerWidgets[widgetID] then
        -- Existing timer removed
        self.timerWidgets[widgetID] = nil

        self.numTimers = self.numTimers - 1

        if self.numTimers == 0 and self.timerBlock.ticker then
            self.timerBlock.ticker:Cancel()
            self.timerBlock.ticker = nil
        end

        if self.numTimers == 0 and self.layoutFunc and self.container then
            self.layoutFunc(self.container)
        end
    end
end

function GwObjectivesScenarioContainerWidgetMixin:RegisterFakeTimerWidget(widgetID, widgetFrame)
    if not self.timerWidgets[widgetID] then
        self.timerWidgets[widgetID] = widgetFrame
        self.numTimers = self.numTimers + 1
    end
end

function GwObjectivesScenarioContainerWidgetMixin:UnregisterFakeTimerWidget(widgetID)
    if self.timerWidgets[widgetID] then
        self.timerWidgets[widgetID] = nil
        self.numTimers = self.numTimers - 1

        if self.numTimers == 0 and self.layoutFunc and self.container then
            self.layoutFunc(self.container)
        end
    end
end

function GwObjectivesScenarioContainerWidgetMixin:SyncWidgetTimerRegistration(widgetID, widgetFrame, widgetInfo)
    local oldIsRealTimer = widgetFrame.isRealTimer
    local oldIsFakeTimer = widgetFrame.isFakeTimer
    local newIsRealTimer = IsRealTimerWidget(widgetInfo)
    local newIsFakeTimer = IsFakeTimerWidget(widgetInfo)

    if oldIsRealTimer and not newIsRealTimer then
        self:UnregisterTimerWidget(widgetID)
    elseif oldIsFakeTimer and not newIsFakeTimer then
        self:UnregisterFakeTimerWidget(widgetID)
    end

    if not oldIsRealTimer and newIsRealTimer then
        self:RegisterTimerWidget(widgetID, widgetFrame)
    elseif not oldIsFakeTimer and newIsFakeTimer then
        self:RegisterFakeTimerWidget(widgetID, widgetFrame)
    end

    widgetFrame.hasTimer = widgetInfo.hasTimer
    widgetFrame.isRealTimer = newIsRealTimer
    widgetFrame.isFakeTimer = newIsFakeTimer
end

function GwObjectivesScenarioContainerWidgetMixin:RemoveWidget(widgetID)
    local widgetFrame = self.widgetFrames[widgetID]
    if not widgetFrame then
        -- This widget was never created. Nothing to do
        return
    end

    -- If this is a widget with a timer, remove it from the timer list
    if widgetFrame.isRealTimer then
        self:UnregisterTimerWidget(widgetID)
    elseif widgetFrame.isFakeTimer then
        self:UnregisterFakeTimerWidget(widgetID)
    end

    self.widgetPools:Release(widgetFrame)
    self.widgetFrames[widgetID] = nil
end

function GwObjectivesScenarioContainerWidgetMixin:OnEvent(event, ...)
    if event == "UPDATE_ALL_UI_WIDGETS" then
        self:ProcessAllWidgets()
    elseif event == "UPDATE_UI_WIDGET" then
        local widgetInfo = ...
        if self:IsRegisteredForWidgetSet(widgetInfo.widgetSetID) then
            self:ProcessWidget(widgetInfo.widgetID, widgetInfo.widgetType)
        end
    end
end

function GwObjectivesScenarioContainerWidgetMixin:IsRegisteredForWidgetSet(widgetSetID)
    if widgetSetID then
        return self.widgetSetID == widgetSetID
    else
        return self.widgetSetID ~= nil
    end
end

function GwObjectivesScenarioContainerWidgetMixin:GetWidgetFromPools(templateInfo)
    if templateInfo then
        return self.widgetPools:Acquire()
    end
end

function GwObjectivesScenarioContainerWidgetMixin:CreateWidget(widgetID, widgetType, widgetTypeInfo, widgetInfo)
    local widgetFrame = self:GetWidgetFromPools(widgetTypeInfo.templateInfo)

    widgetFrame.widgetID = widgetID
    widgetFrame.widgetSetID = self.widgetSetID
    widgetFrame.widgetType = widgetType
    widgetFrame.isRealTimer = false
    widgetFrame.isFakeTimer = false
    self:SyncWidgetTimerRegistration(widgetID, widgetFrame, widgetInfo)

    self.widgetFrames[widgetID] = widgetFrame

    return widgetFrame
end

function GwObjectivesScenarioContainerWidgetMixin:HandleTimer(timerBlock, widgetInfo)
    if widgetInfo.shownState ~= Enum.WidgetShownState.Hidden then
        local timerValue = Clamp(widgetInfo.timerValue, widgetInfo.timerMin, widgetInfo.timerMax)
        local timeRemaining = timerValue - widgetInfo.timerMin
        local timerText = SecondsToClock(timeRemaining)

        timerBlock.timer:SetMinMaxValues(widgetInfo.timerMin, widgetInfo.timerMax)
        timerBlock.timer:SetValue(timerValue)
        timerBlock.timerString:SetText(timerText)

        timerBlock.timer:Show()
    else
        timerBlock.timer:SetShown(timerBlock.needToShowTimer)
        if not timerBlock.needToShowTimer then
            timerBlock.height = 1
        end
    end
end

function GwObjectivesScenarioContainerWidgetMixin:IsTimerRunning()
    return (self.numTimers or 0) > 0
end

function GwObjectivesScenarioContainerWidgetMixin:ProcessWidget(widgetID, widgetType)
    local widgetTypeInfo = UIWidgetManager:GetWidgetTypeInfo(widgetType)
    if not widgetTypeInfo then
        -- This WidgetType is not supported (nothing called RegisterWidgetVisTypeTemplate for it)
        return
    end

    local widgetInfo = widgetTypeInfo.visInfoDataFunction(widgetID)
    local widgetFrame = self.widgetFrames[widgetID]
    local widgetAlreadyExisted = (widgetFrame ~= nil)

    if widgetAlreadyExisted and widgetFrame.widgetType ~= widgetType then
        -- This widget existed already but the type has changed, so release the old widget frame and treat it as a brand new widget
        self:RemoveWidget(widgetID)
        widgetAlreadyExisted = false
    end
    local isNewWidget = false
    if widgetAlreadyExisted then
        if not widgetInfo then
            self:RemoveWidget(widgetID)
            return
        end

        self:SyncWidgetTimerRegistration(widgetID, widgetFrame, widgetInfo)
    else
        -- Widget did not already exist
        if widgetInfo then
            -- And it should be shown...create it
            widgetFrame = self:CreateWidget(widgetID, widgetType, widgetTypeInfo, widgetInfo)
            isNewWidget = true
        else
            -- Widget should not be shown. It didn't already exist so there is nothing to do
            return
        end
    end
    if widgetFrame.isRealTimer or widgetFrame.isFakeTimer then
        self:HandleTimer(self.timerBlock, widgetInfo)
    elseif widgetInfo then
        wipe(self.widgetInfoForStatusBar)
        tinsert(self.widgetInfoForStatusBar, widgetInfo)
        if self.layoutFunc then
            self.layoutFunc(self.container)
        end
        return
    end

    if isNewWidget or widgetFrame.isFakeTimer then
        if self.layoutFunc then
            self.layoutFunc(self.container)
        end
    end
end

function GwObjectivesScenarioContainerWidgetMixin:ProcessAllWidgets()
    local setWidgets = C_UIWidgetManager.GetAllWidgetsBySetID(self.widgetSetID)
    for _, widgetInfo in ipairs(setWidgets) do
        self:ProcessWidget(widgetInfo.widgetID, widgetInfo.widgetType)
    end

    if self.layoutFunc then
        self.layoutFunc(self.container)
    end
end

function GwObjectivesScenarioContainerWidgetMixin:UnregisterForWidgetSet()
    if not self.widgetSetID then
        -- We are not registered to a WidgetSet...nothing to do
        return
    end

    self:RemoveAllWidgets()

    self.widgetSetID = nil
    self.widgetID = nil
    self.layoutFunc = nil
    wipe(self.widgetInfoForStatusBar)

    self:UnregisterEvent("UPDATE_ALL_UI_WIDGETS")
    self:UnregisterEvent("UPDATE_UI_WIDGET")
end

function GwObjectivesScenarioContainerWidgetMixin:RegisterForWidgetSet(widgetSetID)
    if self.widgetSetID then
        -- We are already registered to a WidgetSet
        if self.widgetSetID == widgetSetID then
            -- And it's the same WidgetSet we are trying to register again...nothing to do
            return
        else
            -- We are already registered for a different WidgetSet...unregister it
            self:UnregisterForWidgetSet()
        end
    end

    if not widgetSetID then
        return
    end

    self.widgetSetID = widgetSetID
    self.layoutFunc = self.container.UpdateLayout
    self.widgetFrames = {}
    self.timerWidgets = {}
    self.widgetInfoForStatusBar = {}
    self.numTimers = 0

    self:ProcessAllWidgets()

    self:RegisterEvent("UPDATE_ALL_UI_WIDGETS")
    self:RegisterEvent("UPDATE_UI_WIDGET")
end

function GwObjectivesScenarioContainerMixin:ClearWidgetSet()
    self:UpdateWidgetRegistration()
end

function GwObjectivesScenarioContainerMixin:UpdateWidgetRegistration(widgetSetID)
    self.timerWidgetManager:RegisterForWidgetSet(widgetSetID)
    if widgetSetID ~= nil then
        self.statusBarWidgetManager:RegisterForWidgetSet(514)
    else
        self.statusBarWidgetManager:RegisterForWidgetSet()
    end
end

function GwObjectivesScenarioContainerMixin:UpdateLayout(event, ...)
    GwObjectivesNotification:RemoveNotificationOfType(GW.Enum.ObjectivesNotificationType.Scenario)
    GwObjectivesNotification:RemoveNotificationOfType(GW.Enum.ObjectivesNotificationType.Torghast)
    GwObjectivesNotification:RemoveNotificationOfType(GW.Enum.ObjectivesNotificationType.Delve)

    local compassData = {
        TYPE = GW.Enum.ObjectivesNotificationType.Scenario,
        TITLE = "Unknown Scenario",
        ID = "unknown",
        QUESTID = "unknown",
        COMPASS = false,
        MAPID = nil,
        X = nil,
        Y = nil,
        COLOR = GW.Colors.ObjectivesTypeColors[GW.Enum.ObjectivesNotificationType.Scenario],
    }

    local block = self.block
    local timerBlock = self.timerBlock

    block.height = 1

    if timerBlock.timer:IsShown() then
        block.height = timerBlock.height
    end

    block.numObjectives = 0
    block.questLogIndex = 0
    block.groupButton:Hide()
    block.delvesFrame:Hide()
    block:Show()

    local _, _, numStages, _, _, _, _, _, _, _, _, _, scenarioID = C_Scenario.GetInfo()
    if numStages == 0 or (GW.Retail and IsOnGroundFloorInJailersTower()) then
        local name, instanceType, _, difficultyName, _ = GetInstanceInfo()
        if instanceType == "raid" then
            compassData.TITLE = name
            compassData.DESC = difficultyName
            GwObjectivesNotification:AddNotification(compassData)
            block.height = block.height + 5
        else
            GwObjectivesNotification:RemoveNotificationOfType(GW.Enum.ObjectivesNotificationType.Scenario)
            GwObjectivesNotification:RemoveNotificationOfType(GW.Enum.ObjectivesNotificationType.Torghast)
            block:Hide()
        end
        GW.CombatQueue_Queue(nil, block.UpdateObjectiveActionButton, {block})
        if block.hasItem then
            block.fromContainerTopHeight = block.height
            GW.CombatQueue_Queue("update_tracker_scenario_itembutton_position", block.UpdateObjectiveActionButtonPosition, {block})
        end

        for i = (block.numObjectives or 0) + 1, #block.objectiveBlocks do
            block.objectiveBlocks[i]:Hide()
        end

        block:SetHeight(block.height)
        self.oldHeight = GW.RoundInt(self:GetHeight())
        self:SetHeight(block.height)
        if not timerBlock.needToShowTimer then
            timerBlock.timer:Hide()
            timerBlock.height = 1
            timerBlock:SetHeight(timerBlock.height)
        end
        self:ClearWidgetSet()

        return
    end

    local stageName, stageDescription, numCriteria, _, _, _, _, _, allSpellInfo, _, questID, widgetSetID = C_Scenario.GetStepInfo()
    self:UpdateWidgetRegistration(widgetSetID)
    local _, _, difficultyID, difficultyName = GetInstanceInfo()
    local isMythicKeystone = difficultyID == 8
    stageDescription = stageDescription or ""
    stageName = stageName or ""
    if difficultyName then
        local level = GW.Retail and C_ChallengeMode.GetActiveKeystoneInfo() or 0
        if level > 0 then
            compassData.TITLE = stageName .. " |cFFFFFFFF +" .. level .. " " .. difficultyName .. "|r"
        else
            compassData.TITLE = stageName .. " |cFFFFFFFF " .. difficultyName .. "|r"
        end
        compassData.DESC = stageDescription .. " "
    end

    if GW.Retail and IsInJailersTower() then
        local floor = ""
        if event == "JAILERS_TOWER_LEVEL_UPDATE" then
            local level, type, textureKit = ...
            self.jailersTowerLevelUpdateInfo = {level = level, type = type, textureKit = textureKit}
        end
        local widgetInfo = C_UIWidgetManager.GetScenarioHeaderCurrenciesAndBackgroundWidgetVisualizationInfo(3302)
        if widgetInfo then floor = widgetInfo.headerText or "" end

        if self.jailersTowerLevelUpdateInfo and self.jailersTowerLevelUpdateInfo.type then
            local typeString = C_ScenarioInfo.GetJailersTowerTypeString(self.jailersTowerLevelUpdateInfo.type)
            if typeString then
                compassData.TITLE = difficultyName .. " |cFFFFFFFF " .. floor .. " - " .. typeString .. "|r"
            else
                compassData.TITLE = difficultyName .. " |cFFFFFFFF " .. floor .. "|r"
            end
        else
            compassData.TITLE = difficultyName .. " |cFFFFFFFF " .. floor .. "|r"
        end

        compassData.COLOR =GW.Colors.ObjectivesTypeColors[GW.Enum.ObjectivesNotificationType.Torghast]
        compassData.TYPE = GW.Enum.ObjectivesNotificationType.Torghast
    end

    -- check for active delves
    local delvesWidgetInfo = GW.Retail and (difficultyID and difficultyID == 208) and C_UIWidgetManager.GetScenarioHeaderDelvesWidgetVisualizationInfo(6183)
    if delvesWidgetInfo and delvesWidgetInfo.frameTextureKit == "delves-scenario" then
        local tierLevel = delvesWidgetInfo.tierText or ""
        GwObjectivesNotification.iconFrame.tooltipSpellID = delvesWidgetInfo.tierTooltipSpellID
        compassData.TITLE = difficultyName .. " |cFFFFFFFF(" .. tierLevel .. ")|r - " .. delvesWidgetInfo.headerText
        block.delvesFrame:Show()
        block.delvesFrame.reward:Show()
        local id = 1
        for _, spellInfo in ipairs(delvesWidgetInfo.spells) do
            if spellInfo.shownState ~= Enum.WidgetShownState.Hidden then
                local spellData = C_Spell.GetSpellInfo(spellInfo.spellID)
                local spellBlock = block.delvesFrame.spell[id]
                spellBlock.icon:SetTexture(spellData.iconID)
                if not spellBlock.mask then
                    spellBlock.mask = spellBlock:CreateMaskTexture()
                    spellBlock.mask:SetAllPoints(spellBlock.icon)
                    spellBlock.mask:SetTexture("Interface/CHARACTERFRAME/TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
                    spellBlock.icon:AddMaskTexture(spellBlock.mask)
                end
                spellBlock.icon:SetDesaturated(spellInfo.enabledState == Enum.WidgetEnabledState.Disabled)
                spellBlock.spellID = spellData.spellID
                spellBlock:Show()
                id = id + 1
            end
        end

        for i = id, 5 do
            block.delvesFrame.spell[i].spellID = nil
            block.delvesFrame.spell[i]:Hide()
        end

        -- handle death
        if delvesWidgetInfo.currencies and #delvesWidgetInfo.currencies > 0 and delvesWidgetInfo.currencies[1].textEnabledState > 0 then
            block.delvesFrame.deathCounter.tooltip = delvesWidgetInfo.currencies[1].tooltip
            block.delvesFrame.deathCounter.counter:SetText(delvesWidgetInfo.currencies[1].text)
            block.delvesFrame.deathCounter.icon:SetTexture(delvesWidgetInfo.currencies[1].iconFileID)
            block.delvesFrame.deathCounter:Show()
        else
            block.delvesFrame.deathCounter:Hide()
        end

        -- handle rewards
        if delvesWidgetInfo.rewardInfo.shownState ~= Enum.UIWidgetRewardShownState.Hidden then
            local rewardTooltip = (delvesWidgetInfo.rewardInfo.shownState == Enum.UIWidgetRewardShownState.ShownEarned) and delvesWidgetInfo.rewardInfo.earnedTooltip or delvesWidgetInfo.rewardInfo.unearnedTooltip
            block.delvesFrame.reward.tooltip = rewardTooltip
            block.delvesFrame.reward.earned:SetShown(delvesWidgetInfo.rewardInfo.shownState == Enum.UIWidgetRewardShownState.ShownEarned)
            block.delvesFrame.reward.unearned:SetShown(delvesWidgetInfo.rewardInfo.shownState == Enum.UIWidgetRewardShownState.ShownUnearned)
            block.delvesFrame.reward:Show()
        else
            block.delvesFrame.reward:Hide()
        end

        compassData.COLOR = GW.Colors.ObjectivesTypeColors[GW.Enum.ObjectivesNotificationType.Delve]
        compassData.TYPE = GW.Enum.ObjectivesNotificationType.Delve
    end

    block:SetBlockColorByKey(compassData.TYPE)
    block.Header:SetTextColor(block.color.r, block.color.g, block.color.b)
    block.hover:SetVertexColor(block.color.r, block.color.g, block.color.b)
    GwObjectivesNotification:AddNotification(compassData, true)

    if questID then
        block.questLogIndex = (GW.Retail and C_QuestLog.GetLogIndexForQuestID or GetQuestLogIndexByID)(questID)
    end

    --check for groupfinder button and add spells
    if GW.Retail then
        block:UpdateFindGroupButton(scenarioID, true)
        GW.CombatQueue_Queue(nil, block.UpdateScenarioSpell, {block, allSpellInfo})
    end

    for criteriaIndex = 1, numCriteria do
        local scenarioCriteriaInfo = C_ScenarioInfo.GetCriteriaInfo(criteriaIndex)
        if scenarioCriteriaInfo then
            local objectiveType = scenarioCriteriaInfo.isWeightedProgress and "progressbar" or "monster"

            if objectiveType == "progressbar" and not isMythicKeystone then
                scenarioCriteriaInfo.totalQuantity = 100
            end

            local mythicKeystoneCurrentValue = 0
            if isMythicKeystone then
                mythicKeystoneCurrentValue = tonumber(string.match(scenarioCriteriaInfo.quantityString, "%d+")) or 1
            end
            block:AddObjective(GW.ParseCriteria(scenarioCriteriaInfo.quantity, scenarioCriteriaInfo.totalQuantity, scenarioCriteriaInfo.description, isMythicKeystone, mythicKeystoneCurrentValue, scenarioCriteriaInfo.isWeightedProgress), criteriaIndex, { finished = false, objectiveType = objectiveType, qty = scenarioCriteriaInfo.quantity, isMythicKeystone = isMythicKeystone, firstObjectivesYValue = -5 })
            if scenarioCriteriaInfo.duration > 0 and scenarioCriteriaInfo.elapsed <= scenarioCriteriaInfo.duration then
                block:AddObjective(TIME_REMAINING, numCriteria + 1, {isQuest = false, qty = nil, totalqty = nil, timerShown = true, duration = scenarioCriteriaInfo.duration, startTime = GetTime() - scenarioCriteriaInfo.elapsed})
            end
        end
    end
    -- add special widgets here
    numCriteria = GW.addWarfrontData(block, numCriteria)
    numCriteria = GW.addHeroicVisionsData(block, numCriteria)
    numCriteria = GW.addJailersTowerData(block, numCriteria)
    numCriteria = GW.addEmberCourtData(self, numCriteria)

    local bonusSteps = C_Scenario.GetBonusSteps() or {}
    local numCriteriaPrev = numCriteria

    for _, v in pairs(bonusSteps) do
        local bonusStepIndex = v
        local _, _, numCriteriaForStep = C_Scenario.GetStepInfo(bonusStepIndex)

        for criteriaIndex = 1, numCriteriaForStep do
            local scenarioCriteriaInfo = C_ScenarioInfo.GetCriteriaInfoByStep(bonusStepIndex, criteriaIndex)
            local objectiveType = scenarioCriteriaInfo.isWeightedProgress and "progressbar" or "monster"

            -- timer bar
            if scenarioCriteriaInfo.duration > 0 and scenarioCriteriaInfo.elapsed <= scenarioCriteriaInfo.duration and not (scenarioCriteriaInfo.failed or scenarioCriteriaInfo.completed) then
                block:AddObjective(TIME_REMAINING, numCriteriaPrev + 1, {isQuest = false, qty = nil, totalqty = nil, timerShown = true, duration = scenarioCriteriaInfo.duration, startTime = GetTime() - scenarioCriteriaInfo.elapsed})
            else
                block:AddObjective(GW.ParseCriteria(scenarioCriteriaInfo.quantity, scenarioCriteriaInfo.totalQuantity, scenarioCriteriaInfo.description), numCriteriaPrev + 1, { finished = false, objectiveType = objectiveType, qty = scenarioCriteriaInfo.quantity, firstObjectivesYValue = -5 })
                numCriteriaPrev = numCriteriaPrev + 1
            end
        end
    end

    --check for additonal statusbar
    for _, widgetInfo in ipairs( self.statusBarWidgetManager.widgetInfoForStatusBar or {} ) do
        if widgetInfo and widgetInfo.shownState ~= Enum.WidgetShownState.Hidden then
            local objectiveType = "object"
            local quantity = widgetInfo.barValue
            local text = GW.ParseCriteria(widgetInfo.barValue, widgetInfo.barMax, widgetInfo.text)

            if widgetInfo.barValueTextType == Enum.StatusBarValueTextType.Percentage then
                objectiveType = "progressbar"
                quantity = math.min(1, widgetInfo.barValue / widgetInfo.barMax)
                text = (widgetInfo.text or "") .. " " .. FormatPercentage(quantity)
                quantity = quantity * 100
            end
            block:AddObjective(text, numCriteriaPrev + 1, { finished = false, objectiveType = objectiveType, qty = quantity, firstObjectivesYValue = -5 })
            numCriteriaPrev = numCriteriaPrev + 1
            if not self.timerWidgetManager:IsTimerRunning() then
                self:ClearWidgetSet()
            end
        end
    end

    for i = (block.numObjectives or 0) + 1, #block.objectiveBlocks do
        block.objectiveBlocks[i]:Hide()
    end

    block.height = block.height + 5
    if block.hasItem then
        block.fromContainerTopHeight = block.height
        GW.CombatQueue_Queue("update_tracker_scenario_itembutton_position", block.UpdateObjectiveActionButtonPosition, {block})
    end

    local intGWQuestTrackerHeight = 0
    if timerBlock.affixeFrame:IsShown() then intGWQuestTrackerHeight = intGWQuestTrackerHeight + 40 end
    if timerBlock.timer:IsShown() then intGWQuestTrackerHeight = intGWQuestTrackerHeight + 40 end

    if self.timerWidgetManager:IsTimerRunning() then
        timerBlock.height = 40
    end

    timerBlock:SetHeight(timerBlock.height)
    block:SetHeight(block.height - intGWQuestTrackerHeight)
    self.oldHeight = GW.RoundInt(self:GetHeight())
    self:SetHeight(block.height)
end

function GwQuesttrackerScenarioBlockMixin:UpdateAffixes(fakeIds)
    if not GW.Retail then return end
    local _, affixes = C_ChallengeMode.GetActiveKeystoneInfo()
    affixes = fakeIds and #fakeIds > 0 and fakeIds or affixes

    for idx, v in pairs(affixes) do
        local affix = self.affixeFrame.affixes[idx]
        local _, _, filedataid = C_ChallengeMode.GetAffixInfo(v)
        if idx == 1 then self.height = self.height + 40 end
        if filedataid then
            affix.icon:SetTexture(filedataid)
            if not affix.mask then
                affix.mask = affix:CreateMaskTexture()
                affix.mask:SetAllPoints(affix.icon)
                affix.mask:SetTexture("Interface/CHARACTERFRAME/TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
                affix.icon:AddMaskTexture(affix.mask)
            end
        end
        affix.affixID = v
        affix:Show()
        self.affixeFrame:Show()
        self.affixeFrame:SetHeight(40) -- needed for anchor points
    end

    if not affixes or (#affixes == 0) then
        for _, v in ipairs(self.affixeFrame.affixes) do
            v.affixID = nil
            v.icon:SetTexture("Interface/AddOns/GW2_UI/textures/icons/icon-boss.png")
        end
        self.affixeFrame:Hide()
        self.affixeFrame:SetHeight(1) -- needed for anchor points
    end
end

function GwQuesttrackerScenarioBlockMixin:UpdateDeathCounter()
    if not GW.Retail then return end
    local count, timeLost = C_ChallengeMode.GetDeathCount()
    self.deathcounter.count = count
    self.deathcounter.timeLost = timeLost
    if timeLost and timeLost > 0 and count and count > 0 then
        self.deathcounter.counterlabel:SetText(count)
        self.deathcounter:Show()
    else
        self.deathcounter:Hide()
    end
end

function GwQuesttrackerScenarioBlockMixin:TimerStop()
    self:SetScript("OnUpdate", nil)
    self.timer:Hide()
    self.chestoverlay:Hide()
    self.deathcounter:Hide()
end

local function GetChallengeModeMapID()
    if GW.Retail then
        return C_ChallengeMode.GetActiveChallengeMapID()
    elseif GW.Mists then
        local _, _, _, _, _, _, _, mapID = GetInstanceInfo()
        return mapID
    end
end

local function GetChallengeModeTimeData(mapID)
    if not mapID then
        return
    end

    if GW.Retail then
        local _, _, timeLimit = C_ChallengeMode.GetMapUIInfo(mapID)
        if not timeLimit or timeLimit <= 0 then
            return
        end
        return timeLimit, timeLimit * TIME_FOR_2, timeLimit * TIME_FOR_3
    end

    local times = C_ChallengeMode.GetChallengeModeMapTimes(mapID)
    if not times or not times[1] or times[1] <= 0 then
        return
    end
    return times[1], times[2], times[3]
end

local function SetupChallengeModeTimer(self, timerID, timeLimit, time2, time3)
    self.chestoverlay:Show()
    self:SetScript("OnUpdate", function()
        local _, elapsedTime = GetWorldElapsedTime(timerID)
        self.timer:SetMinMaxValues(0, timeLimit)
        self.timer:SetValue(math.max(0, timeLimit - elapsedTime))
        self.chestoverlay.chest2:SetShown(elapsedTime < time2)
        self.chestoverlay.chest3:SetShown(elapsedTime < time3)

        if elapsedTime < timeLimit then
            self.timerString:SetText(SecondsToClock(timeLimit - elapsedTime))
            self.timerString:SetTextColor(1, 1, 1)
        else
            self.timerString:SetText(SecondsToClock(0))
            self.timerString:SetTextColor(255, 0, 0)
        end

        if elapsedTime < time3 then
            self.chestoverlay.timerStringChest3:SetText(SecondsToClock(time3 - elapsedTime))
            self.chestoverlay.timerStringChest2:SetText(SecondsToClock(time2 - elapsedTime))
            self.chestoverlay.timerStringChest3:Show()
            self.chestoverlay.timerStringChest2:Show()
        elseif elapsedTime < time2 then
            self.chestoverlay.timerStringChest2:SetText(SecondsToClock(time2 - elapsedTime))
            self.chestoverlay.timerStringChest2:Show()
            self.chestoverlay.timerStringChest3:Hide()
        else
            self.chestoverlay.timerStringChest2:Hide()
            self.chestoverlay.timerStringChest3:Hide()
        end
    end)

    self.timer:Show()
    self.needToShowTimer = true
    self.height = self.height + 50
    self:UpdateAffixes()
    self:UpdateDeathCounter()
end

local function SetupProvingGroundTimer(self, timerID, duration)
    self:SetScript("OnUpdate", function()
        local _, elapsedTime = GetWorldElapsedTime(timerID)
        self.timer:SetMinMaxValues(0, duration)
        self.timer:SetValue(math.max(0, duration - elapsedTime))
        self.timer:SetValue(1 - (elapsedTime / duration))
        self.timerString:SetText(SecondsToClock(duration - elapsedTime))
    end)
    self.timer:Show()
    self.needToShowTimer = true
    self.height = self.height + 40
end

function GwQuesttrackerScenarioBlockMixin:TimerUpdate(...)
    self.height = 1
    local fake = false
    if fake then
        self.timer:Show()
        self.needToShowTimer = true
        self.height = self.height + 50
        self:UpdateAffixes({146})
        self:UpdateDeathCounter()
        self.chestoverlay:Show()
        self.chestoverlay.chest2:Show()
        self.chestoverlay.chest3:Show()
        self.chestoverlay.timerStringChest3:Show()
        self.chestoverlay.timerStringChest2:Show()

        return
    end

    for i = 1, select("#", ...) do
        local timerID = select(i, ...)
        local _, _, wtype = GetWorldElapsedTime(timerID)
        if wtype == Enum.WorldElapsedTimerTypes.ChallengeMode then
            local mapID = GetChallengeModeMapID()
            local timeLimit, time2, time3 = GetChallengeModeTimeData(mapID)
            if timeLimit and time2 and time3 then
                SetupChallengeModeTimer(self, timerID, timeLimit, time2, time3)
                return
            end
        elseif wtype == Enum.WorldElapsedTimerTypes.ProvingGround then
            local _, _, _, duration = C_Scenario.GetProvingGroundsInfo()
            if duration and duration > 0 then
                SetupProvingGroundTimer(self, timerID, duration)
                return
            end
        end
    end

    self.timer:Hide()
    self.chestoverlay:Hide()
    self.deathcounter:Hide()
    self:SetScript("OnUpdate", nil)
    self.needToShowTimer = false

    for _, v in ipairs(self.affixeFrame.affixes) do
        v.affixID = nil
        v.icon:SetTexture("Interface/AddOns/GW2_UI/textures/icons/icon-boss.png")
    end
    self.affixeFrame:Hide()
    self.affixeFrame:SetHeight(1) -- needed for anchor points
end

function GwQuesttrackerScenarioBlockMixin:TimerBlockOnEvent(event, ...)
    if event == "PLAYER_ENTERING_WORLD" or event == nil then
        self:TimerUpdate(GetWorldElapsedTimers())
        self:UpdateDeathCounter()
    elseif event == "WORLD_STATE_TIMER_START" then
        local timerID = ...
        self:TimerUpdate(timerID)
    elseif event == "WORLD_STATE_TIMER_STOP" then
        self:TimerStop()
    elseif event == "PROVING_GROUNDS_SCORE_UPDATE" then
        local score = ...
        self.score.scoreString:SetText(score)
        self.score:Show()
        self.height = self.height + 40
    elseif event == "CHALLENGE_MODE_START" or event == "CHALLENGE_MODE_COMPLETED" or event == "CHALLENGE_MODE_MAPS_UPDATE" or event == "ZONE_CHANGED" then
        self:TimerUpdate(GetWorldElapsedTimers())
    elseif event == "CHALLENGE_MODE_DEATH_COUNT_UPDATED" then
        self:UpdateDeathCounter()
    end
    self:SetHeight(self.height)

    self.container:UpdateLayout()
end

local function UIWidgetTemplateTooltipFrameOnEnter(self)
    if self.tooltip then
        self.tooltipContainsHyperLink = false
        self.preString = nil
        self.hyperLinkString = nil
        self.postString = nil
        self.tooltipContainsHyperLink, self.preString, self.hyperLinkString, self.postString = ExtractHyperlinkString(self.tooltip)

        EmbeddedItemTooltip:SetOwner(self, "ANCHOR_LEFT")

        if self.tooltipContainsHyperLink then
            local clearTooltip = true
            if self.preString and self.preString:len() > 0 then
                EmbeddedItemTooltip:AddLine(self.preString, 1, 1, 1, true)
                clearTooltip = false
            end

            GameTooltip_ShowHyperlink(EmbeddedItemTooltip, self.hyperLinkString, 0, 0, clearTooltip)

            if self.postString and self.postString:len() > 0 then
                GameTooltip_AddColoredLine(EmbeddedItemTooltip, self.postString, self.tooltipColor or HIGHLIGHT_FONT_COLOR, true)
            end

            self.UpdateTooltip = self.OnEnter

            EmbeddedItemTooltip:Show()
        else
            local header, nonHeader = SplitTextIntoHeaderAndNonHeader(self.tooltip)
            if header then
                GameTooltip_AddColoredLine(EmbeddedItemTooltip, header, self.tooltipColor or NORMAL_FONT_COLOR, true)
            end
            if nonHeader then
                GameTooltip_AddColoredLine(EmbeddedItemTooltip, nonHeader, self.tooltipColor or NORMAL_FONT_COLOR, true)
            end
            self.UpdateTooltip = nil

            EmbeddedItemTooltip:SetShown(header ~= nil)
        end
    end
end

function GwObjectivesScenarioContainerMixin:InitModule()
    self:RegisterEvent("PLAYER_ENTERING_WORLD")
    self:RegisterEvent("SCENARIO_UPDATE")
    self:RegisterEvent("SCENARIO_CRITERIA_UPDATE")
    self:RegisterEvent("LOOT_CLOSED")
    self:RegisterEvent("CURRENCY_DISPLAY_UPDATE")
    self:RegisterEvent("ZONE_CHANGED_INDOORS")
    self:RegisterEvent("ZONE_CHANGED_NEW_AREA")
    self:RegisterEvent("ZONE_CHANGED")
    self:RegisterEvent("SCENARIO_COMPLETED")
    self:RegisterEvent("SCENARIO_SPELL_UPDATE")
    self:RegisterEvent("JAILERS_TOWER_LEVEL_UPDATE")
    self:SetScript("OnEvent", self.UpdateLayout)


    self.jailersTowerType = nil

    -- JailersTower hook
    -- do it only here so we are sure we do not hook more than one time
    if GW.Retail then
        hooksecurefunc(ScenarioObjectiveTracker, "SlideInContents", function(container)
            if container:ShouldShowCriteria() and IsInJailersTower() then
                self:UpdateLayout()
            end
        end)
    end

    self.timerBlock = CreateFrame("Button", "GwQuestTrackerTimer", self, "GwQuesttrackerScenarioBlock")
    self.timerBlock.container = self
    self.timerBlock.needToShowTimer = false
    self.timerBlock.height = self.timerBlock:GetHeight()
    self.timerBlock.timerlabel = self.timerBlock.timer.timerlabel
    self.timerBlock.timerString = self.timerBlock.timer.timerString

    self.timerBlock.timerlabel:GwSetFontTemplate(UNIT_NAME_FONT, GW.Enum.TextSizeType.Small)
    self.timerBlock.timerlabel:SetTextColor(1, 1, 1)
    self.timerBlock.timerlabel:SetShadowOffset(1, -1)
    self.timerBlock.timerString:GwSetFontTemplate(UNIT_NAME_FONT, GW.Enum.TextSizeType.Small)
    self.timerBlock.timerString:SetTextColor(1, 1, 1)
    self.timerBlock.timerString:SetShadowOffset(1, -1)

    self.timerBlock.chestoverlay.timerStringChest2:GwSetFontTemplate(UNIT_NAME_FONT, GW.Enum.TextSizeType.Small, nil, -2)
    self.timerBlock.chestoverlay.timerStringChest2:SetTextColor(1, 1, 1)
    self.timerBlock.chestoverlay.timerStringChest2:SetShadowOffset(1, -1)
    self.timerBlock.chestoverlay.timerStringChest3:GwSetFontTemplate(UNIT_NAME_FONT, GW.Enum.TextSizeType.Small, nil, -2)
    self.timerBlock.chestoverlay.timerStringChest3:SetTextColor(1, 1, 1)
    self.timerBlock.chestoverlay.timerStringChest3:SetShadowOffset(1, -1)

    self.timerBlock.chestoverlay.chest2:ClearAllPoints()
    self.timerBlock.chestoverlay.chest3:ClearAllPoints()
    self.timerBlock.chestoverlay.timerStringChest2:ClearAllPoints()
    self.timerBlock.chestoverlay.timerStringChest3:ClearAllPoints()
    self.timerBlock.chestoverlay.chest2:SetPoint("LEFT", self.timerBlock.timer, "LEFT", self.timerBlock.timer:GetWidth() * (1 - TIME_FOR_2) - 1, -6)
    self.timerBlock.chestoverlay.chest3:SetPoint("LEFT", self.timerBlock.timer, "LEFT", self.timerBlock.timer:GetWidth() * (1 - TIME_FOR_3) - 1, -6)
    self.timerBlock.chestoverlay.timerStringChest2:SetPoint("RIGHT", self.timerBlock.chestoverlay.chest2, "LEFT", -2, -6)
    self.timerBlock.chestoverlay.timerStringChest3:SetPoint("RIGHT", self.timerBlock.chestoverlay.chest3, "LEFT", -2, -6)

    self.timerBlock.deathcounter.counterlabel:GwSetFontTemplate(UNIT_NAME_FONT, GW.Enum.TextSizeType.Small, nil, -2)
    self.timerBlock.deathcounter.counterlabel:SetTextColor(1, 1, 1)
    self.timerBlock.deathcounter.counterlabel:SetShadowOffset(1, -1)
    self.timerBlock.score.scoreString:GwSetFontTemplate(UNIT_NAME_FONT, GW.Enum.TextSizeType.Small)

    for _, v in ipairs(self.timerBlock.affixeFrame.affixes) do
        v:SetScript("OnEnter", function(affixBlock)
            if affixBlock.affixID then
                GameTooltip:SetOwner(affixBlock, "ANCHOR_BOTTOMLEFT", 0, 50)
                GameTooltip:ClearLines()
                local name, description = C_ChallengeMode.GetAffixInfo(affixBlock.affixID)
                GameTooltip:SetOwner(affixBlock, "ANCHOR_RIGHT")
                GameTooltip:SetText(name, 1, 1, 1, 1, true)
                GameTooltip:AddLine(description, nil, nil, nil, true)
                GameTooltip:Show()
            end
        end)
        v:SetScript("OnLeave", GameTooltip_Hide)
    end

    self.timerBlock.deathcounter:SetScript("OnEnter", function(deathCounterBlock)
            GameTooltip:SetOwner(deathCounterBlock, "ANCHOR_LEFT")
            GameTooltip:SetText(CHALLENGE_MODE_DEATH_COUNT_TITLE:format(deathCounterBlock.count), 1, 1, 1)
            GameTooltip:AddLine(CHALLENGE_MODE_DEATH_COUNT_DESCRIPTION:format(SecondsToClock(deathCounterBlock.timeLost)))
            GameTooltip:Show()
        end)
    self.timerBlock.deathcounter:SetScript("OnLeave", GameTooltip_Hide)

    self.timerBlock:SetParent(self)
    self.timerBlock:ClearAllPoints()
    self.timerBlock:SetPoint("TOPRIGHT", self, "TOPRIGHT", 0, 0)

    self.timerBlock:RegisterEvent("PLAYER_ENTERING_WORLD")
    self.timerBlock:RegisterEvent("WORLD_STATE_TIMER_START")
    self.timerBlock:RegisterEvent("WORLD_STATE_TIMER_STOP")
    self.timerBlock:RegisterEvent("CHALLENGE_MODE_MAPS_UPDATE")
    self.timerBlock:RegisterEvent("CHALLENGE_MODE_START")
    self.timerBlock:RegisterEvent("CHALLENGE_MODE_COMPLETED")
    self.timerBlock:RegisterEvent("ZONE_CHANGED")
    self.timerBlock:RegisterEvent("CHALLENGE_MODE_DEATH_COUNT_UPDATED")

    if GW.Retail then
        self.timerBlock:RegisterEvent("PROVING_GROUNDS_SCORE_UPDATE")
    end
    self.timerBlock:SetScript("OnEvent", self.timerBlock.TimerBlockOnEvent)

    self.block = self:GetBlock(1, GW.Enum.ObjectivesNotificationType.Scenario, GW.Retail) -- only create an actionbutton for retail here
    if GW.Retail then
        Mixin(self.block.actionButton, ScenarioSpellButtonMixin)
        self.block.actionButton:SetScript("OnEnter", self.block.actionButton.OnEnter)
    end

    self.block:ClearAllPoints()
    self.block:SetPoint("TOPRIGHT", self.timerBlock, "BOTTOMRIGHT", 0, 0)
    self.block.Header:SetText("")
    self.block.delvesFrame:ClearAllPoints()
    self.block.delvesFrame:SetPoint("TOPRIGHT", GwObjectivesNotification, "BOTTOMRIGHT", 0, 35)

    for _, v in ipairs(self.block.delvesFrame.spell) do
        v:SetScript("OnEnter", function(delveBlock)
                if delveBlock.spellID then
                    GameTooltip:SetOwner(delveBlock, "ANCHOR_LEFT")
                    GameTooltip:SetSpellByID(delveBlock.spellID)
                    GameTooltip:Show()
                end
            end)
        v:SetScript("OnLeave", GameTooltip_Hide)
    end

    self.block.delvesFrame.reward:SetScript("OnEnter", UIWidgetTemplateTooltipFrameOnEnter)
    self.block.delvesFrame.reward:SetScript("OnLeave", function() UIWidgetTemplateTooltipFrameMixin:OnLeave() end)

    self.block.delvesFrame.deathCounter:SetScript("OnEnter", UIWidgetTemplateTooltipFrameOnEnter)
    self.block.delvesFrame.deathCounter:SetScript("OnLeave", function() UIWidgetTemplateTooltipFrameMixin:OnLeave() end)
    self.block.delvesFrame.deathCounter.counter:GwSetFontTemplate(UNIT_NAME_FONT, GW.Enum.TextSizeType.Normal)

    local timerWidgetManager = CreateFrame("Frame")
    Mixin(timerWidgetManager, GwObjectivesScenarioContainerWidgetMixin)
    timerWidgetManager.widgetPools = CreateFramePool("Frame")
    timerWidgetManager:SetScript("OnEvent", timerWidgetManager.OnEvent)
    timerWidgetManager.timerBlock = self.timerBlock
    timerWidgetManager.container = self
    self.timerWidgetManager = timerWidgetManager

    local statusBarWidgetManager = CreateFrame("Frame")
    Mixin(statusBarWidgetManager, GwObjectivesScenarioContainerWidgetMixin)
    statusBarWidgetManager.widgetPools = CreateFramePool("Frame")
    statusBarWidgetManager:SetScript("OnEvent", statusBarWidgetManager.OnEvent)
    statusBarWidgetManager.timerBlock = self.timerBlock
    statusBarWidgetManager.container = self
    self.statusBarWidgetManager = statusBarWidgetManager

    C_Timer.After(0.8, function() self:UpdateLayout() end)

    self.timerBlock:TimerBlockOnEvent()
end
