---@class GW2
local GW = select(2, ...)
local L = GW.L
local CommaValue = GW.CommaValue
local lerp = GW.lerp
local Diff = GW.Diff
local animations = GW.animations

local FormatNumber = GW.GetLocalizedNumber or CommaValue or tostring

-- forward function defs
local experiencebarAnimation = 0
local houseInfoCache = {}
local queueTimer

local STATUSBAR_COLORS = {
    Xp = {r = 0.83, g = 0.57, b = 0},
    PetXpBattle = {r = 0.58, g = 0, b = 0.55},
    PetXp = {r = 240 / 255, g = 240 / 255, b = 155 / 255},
    Honor = {r = 1, g = 0.2, b = 0.2},
    House = {r = 0.85, g = 0.7, b = 0.43},
}

local function IsAtMaxLevel()
    if IsPlayerAtEffectiveMaxLevel then
        return IsPlayerAtEffectiveMaxLevel()
    end
    return GW.mylevel >= GetMaxPlayerLevel()
end

local function GetMaxLevel()
    if GetMaxLevelForPlayerExpansion then
        return GetMaxLevelForPlayerExpansion()
    end
    return GetMaxPlayerLevel()
end

local function SetStatusBarHover(bar, enable)
    if not bar then
        return
    end

    if enable then
        if not bar.hoverColor then
            local r, g, b = bar:GetStatusBarColor()
            bar.hoverColor = {r = r, g = g, b = b}
        end
        local c = bar.hoverColor or {r = 1, g = 1, b = 1}
        local t = 0.25
        bar:SetStatusBarColor(
            c.r + (1 - c.r) * t,
            c.g + (1 - c.g) * t,
            c.b + (1 - c.b) * t
        )
    else
        if bar.hoverColor then
            bar:SetStatusBarColor(bar.hoverColor.r, bar.hoverColor.g, bar.hoverColor.b)
        end
    end
end

local function xpbar_OnEnter(self)
    GameTooltip:SetOwner(self, "ANCHOR_CURSOR")
    GameTooltip:ClearLines()

    local isRestingString = IsResting() and not IsAtMaxLevel() and L[" (Resting)"] or ""

    GameTooltip:AddLine(COMBAT_XP_GAIN .. isRestingString, 1, 1, 1)

    for _, line in ipairs(self.tooltip) do
        GameTooltip:AddLine(line, 1, 1, 1)
    end

    GameTooltip:Show()

    SetStatusBarHover(self.ExpBar, true)
    SetStatusBarHover(self.Rested, true)
    SetStatusBarHover(self.RepuBar, true)
    SetStatusBarHover(self.PetBar, true)
    SetStatusBarHover(self.AzeritBar, true)
end

local function xpbar_OnClick()
    if not GW.Retail then
        return
    end

    if not IsAtMaxLevel() then
        if GwLevelingRewards:IsShown() then
            GwLevelingRewards:Hide()
        else
            GwLevelingRewards:Show()
        end
    elseif C_AzeriteEmpoweredItem and C_AzeriteEmpoweredItem.IsHeartOfAzerothEquipped() then
        local heartItemLocation = C_AzeriteItem.FindActiveAzeriteItem()
        if heartItemLocation and heartItemLocation:IsEqualTo(ItemLocation:CreateFromEquipmentSlot(2)) then
            if AzeriteEssenceUI and AzeriteEssenceUI:IsShown() then
                HideUIPanel(AzeriteEssenceUI)
            else
                OpenAzeriteEssenceUIFromItemLocation(heartItemLocation)
            end
        end
    end
end

local function flareAnim(self)
    self.barOverlay.flare:Show()

    GW.AddToAnimation("GwXpFlare", 0, 1, GetTime(), 1,
        function(prog)
            self.barOverlay.flare.texture:SetAlpha(1)
            self.barOverlay.flare.texture:SetRotation(lerp(0, 3, prog))
            if prog > 0.75 then
                self.barOverlay.flare.texture:SetAlpha(lerp(1, 0, math.sin(((prog - 0.75) / 0.25) * math.pi * 0.5)))
            end
        end,
        nil,
        function()
            self.barOverlay.flare:Hide()
        end
    )
end

local function UpdatePetXPBattle(self, index, level)
    local name = C_PetBattles.GetName(Enum.BattlePetOwner.Ally, index)
    local rarity = C_PetBattles.GetBreedQuality(Enum.BattlePetOwner.Ally, index)
    local cur, max = C_PetBattles.GetXP(Enum.BattlePetOwner.Ally, index)
    local valPrec = (max > 0) and (cur / max) or 0

    local color = STATUSBAR_COLORS.PetXpBattle
    self.ExpBar:SetStatusBarColor(color.r, color.g, color.b)

    tinsert(self.tooltip, string.format("%s: %s / %s |cffa6a6a6 (%d%%)|r",
        ITEM_QUALITY_COLORS[rarity].color:WrapTextInColorCode(name),
        math.min(max, cur), max,
        math.floor(valPrec * 100)
    ))

    return true, valPrec, level + 1
end

local function UpdateXPValues(self)
    local valCurrent = UnitXP("player")
    local valMax = UnitXPMax("player")
    local valPrec = (valMax > 0) and (valCurrent / valMax) or 0
    local rested = GetXPExhaustion() or 0
    local restedPrec = 0
    if rested > 0 then
        restedPrec = math.min(rested / (valMax - valCurrent), 1)
    end

    local color = STATUSBAR_COLORS.Xp
    self.ExpBar:SetStatusBarColor(color.r, color.g, color.b)

    if not IsAtMaxLevel() then
        tinsert(self.tooltip, COMBAT_XP_GAIN .. " " .. FormatNumber(valCurrent) .. " / " ..
            FormatNumber(valMax) .. " |cffa6a6a6 (" .. math.floor(valPrec * 100) .. "%)|r")

        if rested > 0 then
            tinsert(self.tooltip, L["Rested "] .. FormatNumber(rested) .. " |cffa6a6a6 (" .. math.floor((rested / valMax) * 100) .. "%) |r")
        end
    end

    return true, valPrec, restedPrec
end

local function UpdateReputation(self, data, lockLevelTextUnderMaxLevel)
    local showRepu = false
    local valPrecRepu = 0
    local repuLevel, repuNextLevel = nil, nil
    local level, nextLevel
    local friendReputationInfo = C_GossipInfo.GetFriendshipReputation(data.factionID)
    local isParagon, isFriend, isMajor, isNormal = false, false, false, false
    showRepu = true

    if C_Reputation.IsFactionParagonForCurrentPlayer and C_Reputation.IsFactionParagonForCurrentPlayer(data.factionID) then
        local currentValue, maxValueParagon = C_Reputation.GetFactionParagonInfo(data.factionID)
        currentValue = currentValue % maxValueParagon
        valPrecRepu = (maxValueParagon > 0) and (currentValue / maxValueParagon) or 0
        tinsert(self.tooltip, string.format("%s %s %s / %s |cffa6a6a6 (%d%%)|r",
            data.name, REPUTATION,
            FormatNumber(currentValue), FormatNumber(maxValueParagon), math.floor(valPrecRepu * 100)))
        self.RepuBar:SetStatusBarColor(GW.Colors.FactionBarColors[9]:GetRGB())
        isParagon = true
        isFriend = (friendReputationInfo and friendReputationInfo.friendshipFactionID > 0)
    elseif friendReputationInfo and friendReputationInfo.friendshipFactionID > 0 then
        if friendReputationInfo.nextThreshold then
            valPrecRepu = (friendReputationInfo.standing - friendReputationInfo.reactionThreshold) /
                            (friendReputationInfo.nextThreshold - friendReputationInfo.reactionThreshold)
            tinsert(self.tooltip, string.format("%s %s %s / %s |cffa6a6a6 (%d%%)|r",
                friendReputationInfo.name, REPUTATION,
                FormatNumber(friendReputationInfo.standing - friendReputationInfo.reactionThreshold),
                FormatNumber(friendReputationInfo.nextThreshold - friendReputationInfo.reactionThreshold),
                math.floor(valPrecRepu * 100)))
        else
            valPrecRepu = 1
            tinsert(self.tooltip, string.format("%s %s %s / %s |cffa6a6a6 (%d%%)|r",
                friendReputationInfo.name, REPUTATION,
                FormatNumber(friendReputationInfo.maxRep), FormatNumber(friendReputationInfo.maxRep), 100))
        end
        self.RepuBar:SetStatusBarColor(GW.Colors.FactionBarColors[5]:GetRGB())
        isFriend = true
    elseif C_Reputation.IsMajorFaction and C_Reputation.IsMajorFaction(data.factionID) then
        local majorFactionData = C_MajorFactions.GetMajorFactionData(data.factionID)
        if majorFactionData then
            repuLevel = majorFactionData.renownLevel
            repuNextLevel = C_MajorFactions.HasMaximumRenown(data.factionID) and majorFactionData.renownLevel or (majorFactionData.renownLevel + 1)
            if C_MajorFactions.HasMaximumRenown(data.factionID) then
                valPrecRepu = 1
            else
                valPrecRepu = (majorFactionData.renownReputationEarned or 0) / majorFactionData.renownLevelThreshold
            end
            tinsert(self.tooltip, string.format("%s %s %s / %s |cffa6a6a6 (%d%%)|r",
                data.name, REPUTATION,
                FormatNumber(majorFactionData.renownReputationEarned or 0),
                FormatNumber(majorFactionData.renownLevelThreshold),
                math.floor(valPrecRepu * 100)))
            self.RepuBar:SetStatusBarColor(GW.Colors.FactionBarColors[11]:GetRGB())
            isMajor = true
        end
    else
        local currentStanding = data.currentStanding or 0
        local currentThreshold = data.currentReactionThreshold or 0
        local nextThreshold = data.nextReactionThreshold or 0
        if (currentStanding - currentThreshold) == 0 then
            valPrecRepu = 1
            tinsert(self.tooltip, string.format("%s %s 21,000 / 21,000 |cffa6a6a6 (%d%%)|r", data.name, REPUTATION, 100))
        else
            valPrecRepu = (currentStanding - currentThreshold) / (nextThreshold - currentThreshold)
            tinsert(self.tooltip, string.format("%s %s %s / %s |cffa6a6a6 (%d%%)|r",
                data.name, REPUTATION,
                FormatNumber(currentStanding - currentThreshold),
                FormatNumber(nextThreshold - currentThreshold),
                math.floor(valPrecRepu * 100)))
        end
        local reaction = data.reaction or 1
        self.RepuBar:SetStatusBarColor(GW.Colors.FactionBarColors[reaction]:GetRGB())
        isNormal = true
    end

    if not lockLevelTextUnderMaxLevel then
        local nextId = (data.reaction and data.reaction + 1) or 1
        level = isMajor and repuLevel or
            isFriend and friendReputationInfo.reaction or
            isParagon and L["Paragon"] or
            isNormal and getglobal("FACTION_STANDING_LABEL" .. (data.reaction or 1))
        nextLevel = isParagon and L["Paragon"] or
            isFriend and "" or
            isMajor and repuNextLevel or
            isNormal and getglobal("FACTION_STANDING_LABEL" .. math.min(8, nextId))
    end

    return showRepu, valPrecRepu, level, nextLevel
end

local function UpdateAzerite(self, azeriteItem)
    local AzeritVal, AzeritLevel = 0, 0
    local azeriteXP, xpForNextPoint = C_AzeriteItem.GetAzeriteItemXPInfo(azeriteItem)
    AzeritLevel = C_AzeriteItem.GetPowerLevel(azeriteItem)
    AzeritVal = (xpForNextPoint > 0) and (azeriteXP / xpForNextPoint) or 0
    self.AzeritBar:SetStatusBarColor(GW.Colors.FactionBarColors[10]:GetRGB())
    self.AzeritBar.animation:Show()

    tinsert(self.tooltip, AZERITE_POWER_BAR:format(
        FormatNumber(azeriteXP) .. " / " .. FormatNumber(xpForNextPoint) .. " |cffa6a6a6 (" .. math.floor(AzeritVal * 100) .. "%)|r"
    ))

    return true, AzeritVal, AzeritLevel
end

local function UpdateHouseXP(self, data)
    local level = data.houseLevel
    local cur = data.houseFavor
    local max = C_Housing.GetHouseLevelFavorForLevel(level + 1)
    local valPrec = (max > 0) and (math.min(cur, max) / max) or 0

    tinsert(self.tooltip, string.format("%s (%s): %s / %s |cffa6a6a6 (%d%%)|r",
        format(HOUSING_DASHBOARD_OWNERS_HOUSE, data.houseName or GW.myname),
        format(LEVEL .. " %s", data.houseLevel),
        math.min(max, cur), max,
        math.floor(valPrec * 100)
    ))

    if cur >= max then
        tinsert(self.tooltip, HOUSING_DASHBOARD_VISIT_NPC)
    end

    local color = STATUSBAR_COLORS.House
    self.ExpBar:SetStatusBarColor(color.r, color.g, color.b)

    return true, valPrec, level, level + 1
end

local function UpdateHonor(self)
    local level = UnitHonorLevel("player")
    local currentHonor = UnitHonor("player")
    local maxHonor = UnitHonorMax("player")
    local valPrec = (maxHonor > 0) and (currentHonor / maxHonor) or 0

    tinsert(self.tooltip, HONOR .. " " .. FormatNumber(currentHonor) .. " / " .. FormatNumber(maxHonor) .. " |cffa6a6a6 (" .. math.floor(valPrec * 100) .. "%)|r")

    local color = STATUSBAR_COLORS.Honor
    self.ExpBar:SetStatusBarColor(color.r, color.g, color.b)

    return true, valPrec, level, level + 1
end

local function UpdatePetXPClassic(self, level)
    local currXP, nextXP = GetPetExperience()
    local valPrecPet = (nextXP > 0) and (currXP / nextXP) or 0

    tinsert(self.tooltip, PET .. " " .. FormatNumber(currXP) .. " / " .. FormatNumber(nextXP) .. " |cffa6a6a6 (" .. math.floor(valPrecPet * 100) .. "%)|r")

    local color = STATUSBAR_COLORS.PetXp
    self.PetBar:SetStatusBarColor(color.r, color.g, color.b)

    return true, valPrecPet, level
end

local function SetBarLayout(bar, candy, spark, height, offset)
    bar:Show()
    candy:Show()
    spark:Show()
    bar:SetHeight(height)
    candy:SetHeight(height)
    spark:SetHeight(height)
    candy:ClearAllPoints()
    candy:SetPoint("TOPLEFT", 90, offset)
    candy:SetPoint("TOPRIGHT", -90, offset)
    bar:ClearAllPoints()
    bar:SetPoint("TOPLEFT", 90, offset)
    bar:SetPoint("TOPRIGHT", -90, offset)
end

local function AddBar(visibleBars, show, bar, candy, spark, onHide)
    if show then
        table.insert(visibleBars, {bar = bar, candy = candy, spark = spark})
    else
        bar:Hide()
        candy:Hide()
        spark:Hide()
        bar:SetValue(0)
        candy:SetValue(0)
        if onHide then
            onHide()
        end
    end
end

local function LayoutBars(self, showExp, showSecondary, showRepu)
    local visibleBars = {}

    AddBar(visibleBars, showExp, self.ExpBar, self.ExpBarCandy, self.ExpBar.Spark)
    if GW.Retail then
        AddBar(visibleBars, showSecondary, self.AzeritBar, self.AzeritBarCandy, self.AzeritBar.Spark, function()
            self.AzeritBar.animation:Hide()
        end)
    else
        AddBar(visibleBars, showSecondary, self.PetBar, self.PetBarCandy, self.PetBar.Spark)
    end
    AddBar(visibleBars, showRepu, self.RepuBar, self.RepuBarCandy, self.RepuBar.Spark)

    local count = #visibleBars
    local height = 0
    if count == 3 then
        height = 2.66
    elseif count == 2 then
        height = 4
    elseif count == 1 then
        height = 8
    end

    for i, barData in ipairs(visibleBars) do
        local offset = -4 * i
        SetBarLayout(barData.bar, barData.candy, barData.spark, height, offset)
    end
end

local function UpdateData(self)
    local animationSpeed = 15
    local maxPlayerLevel = GetMaxLevel()
    local restingIconString = IsResting() and " |TInterface/AddOns/GW2_UI/textures/icons/resting-icon.png:16:16:0:0|t " or ""
    local showExp, showRepu, showAzerite, showPet = false, false, false, false
    local level = GW.mylevel
    local nextLevel = GW.mylevel < maxPlayerLevel and (GW.mylevel + 1) or GW.mylevel
    local lockLevelTextUnderMaxLevel = level < nextLevel
    local valPrec, rested = 0, 0
    local valPrecRepu, valPrecAzerite, azeritLevel, valPrecPet

    wipe(self.tooltip)

    if not (GW.Classic or GW.TBC or GW.Wrath) and C_PetBattles.IsInBattle() then
        local i = C_PetBattles.GetActivePet(Enum.BattlePetOwner.Ally)
        level = C_PetBattles.GetLevel(Enum.BattlePetOwner.Ally, i)
        if level and level < 25 then
            showExp, valPrec, nextLevel = UpdatePetXPBattle(self, i, level)
        end
    else
        local xpDisabled = IsXPUserDisabled() or false
        if not xpDisabled and not IsAtMaxLevel() then
            showExp, valPrec, rested = UpdateXPValues(self)
        end

        if GW.Retail and not showExp and (IsWatchingHonorAsXP() or C_PvP.IsActiveBattlefield() or IsInActiveWorldPVP()) then
            showExp, valPrec, level, nextLevel = UpdateHonor(self)
        end

        if GW.Retail then
            local guid = C_Housing.GetTrackedHouseGuid()
            if not showExp and guid and houseInfoCache[guid] and houseInfoCache[guid].houseLevel then
                showExp, valPrec, level, nextLevel = UpdateHouseXP(self, houseInfoCache[guid])
            end
        end

        local data = GW.GetWatchedFactionInfo()
        if data and data.factionID and data.factionID > 0 then
            local tempLevel, tempNextLevel = level, nextLevel
            showRepu, valPrecRepu, level, nextLevel = UpdateReputation(self, data, lockLevelTextUnderMaxLevel)
            if not level then
                level = tempLevel
            end
            if not nextLevel then
                nextLevel = tempNextLevel
            end
        end

        if not GW.Retail and select(2, HasPetUI()) and (UnitLevel("pet") or 0) < level then
            showPet, valPrecPet = UpdatePetXPClassic(self, level)
        end

        if GW.Retail and not C_AzeriteItem.IsAzeriteItemAtMaxLevel() then
            local azeriteItem = C_AzeriteItem.FindActiveAzeriteItem()
            if azeriteItem and azeriteItem:IsEquipmentSlot() and C_AzeriteItem.IsAzeriteItemEnabled(azeriteItem) then
                showAzerite, valPrecAzerite, azeritLevel = UpdateAzerite(self, azeriteItem)
            end
        end
    end

    if showAzerite then
        self.AzeritBarCandy:SetValue(valPrecAzerite)
        GW.AddToAnimation("AzeritBarAnimation", self.AzeritBar.AzeritBarAnimation, valPrecAzerite, GetTime(), animationSpeed,
            function(p)
                self.AzeritBar.Spark:SetWidth(math.max(8, math.min(9, self.AzeritBar:GetWidth() * p)))
                self.AzeritBar:SetValue(p)
                self.AzeritBar.Spark:SetPoint("LEFT", self.AzeritBar:GetWidth() * p - 8, 0)
            end)
        self.AzeritBar.AzeritBarAnimation = valPrecAzerite

        if maxPlayerLevel == GW.mylevel then
            level = azeritLevel
            nextLevel = azeritLevel + 1
            self.NextLevel:SetTextColor(240 / 255, 189 / 255, 103 / 255)
            self.CurrentLevel:SetTextColor(240 / 255, 189 / 255, 103 / 255)
            self.labelRight:SetTexture("Interface/AddOns/GW2_UI/textures/hud/level-label-azerit.png")
            self.labelLeft:SetTexture("Interface/AddOns/GW2_UI/textures/hud/level-label-azerit.png")
        end
    else
        local texture = (GW.Retail and maxPlayerLevel == GW.mylevel) and "Interface/AddOns/GW2_UI/textures/hud/level-label-azerit.png" or "Interface/AddOns/GW2_UI/textures/hud/level-label.png"
        self.NextLevel:SetTextColor(1, 1, 1)
        self.CurrentLevel:SetTextColor(1, 1, 1)
        self.labelRight:SetTexture(texture)
        self.labelLeft:SetTexture(texture)
    end

    if showExp then
        local GainBigExp = false
        local FlareBreakPoint = math.max(0.05, 0.15 * (1 - (GW.mylevel / maxPlayerLevel)))
        if (valPrec - experiencebarAnimation) > FlareBreakPoint then
            GainBigExp = true
            flareAnim(self)
        end

        if experiencebarAnimation > valPrec then
            experiencebarAnimation = 0
        end

        self.barOverlay.flare.soundCooldown = 0
        local expSoundCooldown = 0
        local startTime = GetTime()
        animationSpeed = Diff(experiencebarAnimation, valPrec)
        animationSpeed = math.min(15, math.max(5, 10 * animationSpeed))

        GW.AddToAnimation("experiencebarAnimation", experiencebarAnimation, valPrec, GetTime(), animationSpeed, function(step)
            self.ExpBar.Spark:SetWidth(math.max(8, math.min(9, self.ExpBar:GetWidth() * step)))
            if not GainBigExp then
                self.ExpBar:SetValue(step)
                self.ExpBar.Spark:SetPoint("LEFT", self.ExpBar:GetWidth() * step - 8, 0)
                local flarePoint = ((UIParent:GetWidth() - 180) * step) + 90
                self.barOverlay.flare:SetPoint("CENTER", self, "LEFT", flarePoint, 0)
            end
            self.ExpBar.Rested:SetValue(rested)
            self.ExpBar.Rested:SetPoint("LEFT", self.ExpBar, "LEFT", self.ExpBar:GetWidth() * step, 0)

            if GainBigExp and self.barOverlay.flare.soundCooldown < GetTime() then
                expSoundCooldown = math.max(0.1, lerp(0.1, 2, math.sin((GetTime() - startTime) / animationSpeed) * math.pi * 0.5))
                self.ExpBar:SetValue(step)
                self.ExpBar.Spark:SetPoint("LEFT", self.ExpBar:GetWidth() * step - 8, 0)
                local flarePoint = ((UIParent:GetWidth() - 180) * step) + 90
                self.barOverlay.flare:SetPoint("CENTER", self, "LEFT", flarePoint, 0)
                self.barOverlay.flare.soundCooldown = GetTime() + expSoundCooldown
                PlaySoundFile("Interface/AddOns/GW2_UI/Sounds/exp_gain_ping.ogg", "SFX")
                animations.experiencebarAnimation.from = step
            end
        end)
        GW.AddToAnimation("GwExperienceBarCandy", experiencebarAnimation, valPrec, GetTime(), 0.3, function(p)
            self.ExpBarCandy:SetValue(p)
        end)
    end

    if showPet then
        self.PetBarCandy:SetValue(valPrecPet)
        GW.AddToAnimation(
            "petBarAnimation",
            self.PetBar.petBarAnimation,
            valPrecPet,
            GetTime(),
            animationSpeed,
            function()
                self.PetBar.Spark:SetWidth(math.max(8, math.min(9, self.PetBar:GetWidth() * animations.petBarAnimation.progress)))
                self.PetBar:SetValue(animations.petBarAnimation.progress)
                self.PetBar.Spark:SetPoint("LEFT", self.PetBar:GetWidth() * animations.petBarAnimation.progress - 8, 0)
            end
        )
        self.PetBar.petBarAnimation = valPrecPet
    end

    if showRepu then
        animationSpeed = 15
        self.RepuBarCandy:SetValue(valPrecRepu)
        GW.AddToAnimation("repuBarAnimation", self.RepuBar.repuBarAnimation, valPrecRepu, GetTime(), animationSpeed, function(p)
            self.RepuBar.Spark:SetWidth(math.max(8, math.min(9, self.RepuBar:GetWidth() * p)))
            self.RepuBar:SetValue(p)
            self.RepuBar.Spark:SetPoint("LEFT", self.RepuBar:GetWidth() * p - 8, 0)
        end)
        self.RepuBar.repuBarAnimation = valPrecRepu
    end

    local showSecondary = GW.Retail and showAzerite or showPet
    LayoutBars(self, showExp, showSecondary, showRepu)

    experiencebarAnimation = valPrec

    if GW.Retail and GW.IsUpcomingSpellAvalible() then
        nextLevel = nextLevel .. " |TInterface/AddOns/GW2_UI/textures/icons/levelreward-icon.png:20:20:0:0|t"
    end

    local effectiveLevel = GW.UnitEffectiveLevel("player")
    if GW.mylevel ~= effectiveLevel then
        level = level .. " |cFF00FF00(" .. effectiveLevel .. ")|r"
    end

    self.NextLevel:SetText(nextLevel)
    self.CurrentLevel:SetText(restingIconString .. level)
    self.expBarShouldShow = showExp
    self.secondaryBarShouldShow = showSecondary
    self.repuBarShouldShow = showRepu
end

local function queueUpdate(self)
    UpdateData(self)
    queueTimer = nil
end

local function xpbar_OnEvent(self, event, ...)
    if event == "PLAYER_EQUIPMENT_CHANGED" then
        local slot = ...
        if slot == Enum.InventoryType.IndexNeckType then
            if not queueTimer then
                queueTimer = C_Timer.NewTimer(0.1, function() queueUpdate(self) end)
            end
        end
        return
    elseif event == "HOUSE_LEVEL_FAVOR_UPDATED" then
        local info = ...
        houseInfoCache[info.houseGUID] = houseInfoCache[info.houseGUID] or {}
        houseInfoCache[info.houseGUID].houseLevel = info.houseLevel
        houseInfoCache[info.houseGUID].houseFavor = info.houseFavor

        if not queueTimer then
            queueTimer = C_Timer.NewTimer(0.1, function() queueUpdate(self) end)
        end
    elseif event == "PLAYER_HOUSE_LIST_UPDATED" then
        local info = ...
        for _, data in next, info do
            houseInfoCache[data.houseGUID] = houseInfoCache[data.houseGUID] or {}
            houseInfoCache[data.houseGUID].houseName = data.houseName
        end

        local guid = C_Housing.GetTrackedHouseGuid()
        if guid and houseInfoCache[guid].houseLevel and not queueTimer then
            queueTimer = C_Timer.NewTimer(0.1, function() queueUpdate(self) end)
        end
    elseif event == "TRACKED_HOUSE_CHANGED" then
        local guid = ...
        if guid then
            C_Housing.GetCurrentHouseLevelFavor(guid)
        else
            if not queueTimer then
                queueTimer = C_Timer.NewTimer(0.1, function() queueUpdate(self) end)
            end
        end
    else
        if not queueTimer then
            queueTimer = C_Timer.NewTimer(0.1, function() queueUpdate(self) end)
        end
    end
end

local function animateAzeriteBar(self, elapsed)
    local parent = self:GetParent():GetParent()
    local AzeritBar = parent.AzeritBar
    local spark = AzeritBar.Spark
    local value = AzeritBar:GetValue()

    self:SetPoint("RIGHT", spark, "RIGHT", 0, 0)

    local speed = 0.01
    self.prog = self.prog + speed * elapsed
    if self.prog > 1 then
        self.prog = 0
    end
    local prog = self.prog

    self.texture1:SetTexCoord(0, value, 0, 1)
    self.texture2:SetTexCoord(value, 0, 1, 0)

    if prog < 0.2 then
        self.texture2:SetVertexColor(1, 1, 1, lerp(0, 1, prog / 0.2))
    elseif prog > 0.8 then
        self.texture2:SetVertexColor(1, 1, 1, lerp(1, 0, (prog - 0.8) / 0.2))
    end

    if prog < 0.5 then
        self.texture1:SetVertexColor(1, 1, 1, lerp(0, 0.3, prog / 0.5))
    else
        self.texture1:SetVertexColor(1, 1, 1, lerp(0.3, 0, (prog - 0.5) / 0.5))
    end

    self.texture2:SetTexCoord(1 - prog, prog, 1, 0)
end

local function updateBarSize(self)
    local m = (UIParent:GetWidth() - 180) / 10
    local i = 1
    for _, v in ipairs(self.barOverlay.barSep) do
        local rm = (m * i) + 90
        v:ClearAllPoints()
        v:SetPoint("LEFT", self, "LEFT", rm, 0)
        i = i + 1
    end

    m = (UIParent:GetWidth() - 180)
    self.barOverlay.dubbleBarSep:SetWidth(m)
    self.barOverlay.dubbleBarSep:ClearAllPoints()
    self.barOverlay.dubbleBarSep:SetPoint("LEFT", self, "LEFT", 90, 0)
end

local function LoadXPBar()
    local experiencebar = CreateFrame("Frame", "GwExperienceFrame", UIParent, "GwExperienceBar")
    experiencebar.tooltip = {}

    if PetBattleFrame then
        PetBattleFrame.BottomFrame:SetPoint("BOTTOM", PetBattleFrame, "BOTTOM", 0, 14)
        PetBattleFrame.BottomFrame:SetFrameLevel(PetBattleFrame.BottomFrame:GetFrameLevel() + 5)
    end

    if GW.Retail or GW.TBC then
        StatusTrackingBarManager:GwKill()
    end

    if GW.Retail then
        PetBattleFrameXPBar:GwKill()
        GW.LoadUpcomingSpells()

        experiencebar.rightButton:SetScript("OnClick", xpbar_OnClick)
        experiencebar.rightButton:SetScript(
            "OnEnter",
            function(self)
                local p = self:GetParent()
                p.NextLevel.oldColor = {}
                p.NextLevel.oldColor.r, p.NextLevel.oldColor.g, p.NextLevel.oldColor.b = p.NextLevel:GetTextColor()
                p.NextLevel:SetTextColor(p.NextLevel.oldColor.r * 2, p.NextLevel.oldColor.g * 2, p.NextLevel.oldColor.b * 2)
            end
        )
        experiencebar.rightButton:SetScript(
            "OnLeave",
            function(self)
                local p = self:GetParent()
                if p.NextLevel.oldColor == nil then
                    return
                end
                p.NextLevel:SetTextColor(p.NextLevel.oldColor.r, p.NextLevel.oldColor.g, p.NextLevel.oldColor.b)
            end
        )

        experiencebar.AzeritBar.animation:SetScript(
            "OnShow",
            function(self)
                self:SetScript(
                    "OnUpdate",
                    function(self, elapsed)
                        animateAzeriteBar(self, elapsed)
                    end
                )
            end
        )
        experiencebar.AzeritBar.animation:SetScript(
            "OnHide",
            function(self)
                self:SetScript("OnUpdate", nil)
            end
        )

        experiencebar.PetBar:Hide()
        experiencebar.PetBarCandy:Hide()
    else
        experiencebar.AzeritBar:Hide()
        experiencebar.AzeritBarCandy:Hide()
    end

    experiencebarAnimation = UnitXP("Player") / UnitXPMax("Player")

    experiencebar.RepuBar.repuBarAnimation = 0
    experiencebar.PetBar.petBarAnimation = 0
    experiencebar.AzeritBar.AzeritBarAnimation = 0
    experiencebar.NextLevel:GwSetFontTemplate(UNIT_NAME_FONT, GW.Enum.TextSizeType.Normal)
    experiencebar.CurrentLevel:GwSetFontTemplate(UNIT_NAME_FONT, GW.Enum.TextSizeType.Normal)

    updateBarSize(experiencebar)

    experiencebar:SetScript("OnEvent", xpbar_OnEvent)

    experiencebar:RegisterEvent("UPDATE_FACTION")
    experiencebar:RegisterEvent("PLAYER_XP_UPDATE")
    experiencebar:RegisterEvent("PLAYER_UPDATE_RESTING")
    experiencebar:RegisterEvent("UPDATE_EXHAUSTION")
    experiencebar:RegisterEvent("PLAYER_LEVEL_UP")
    experiencebar:RegisterEvent("DISABLE_XP_GAIN")
    experiencebar:RegisterEvent("ENABLE_XP_GAIN")

    if GW.Retail then
        experiencebar:RegisterEvent("PET_BATTLE_CLOSE")
        experiencebar:RegisterEvent("PET_BATTLE_OPENING_START")
        experiencebar:RegisterEvent("HONOR_XP_UPDATE")
        experiencebar:RegisterEvent("ZONE_CHANGED")
        experiencebar:RegisterEvent("ZONE_CHANGED_NEW_AREA")
        experiencebar:RegisterEvent("AZERITE_ITEM_EXPERIENCE_CHANGED")
        experiencebar:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
        experiencebar:RegisterEvent("UPDATE_EXPANSION_LEVEL")
        experiencebar:RegisterEvent("HOUSE_LEVEL_FAVOR_UPDATED")
        experiencebar:RegisterEvent("PLAYER_HOUSE_LIST_UPDATED")
        experiencebar:RegisterEvent("TRACKED_HOUSE_CHANGED")
    else
        experiencebar:RegisterEvent("UNIT_PET_EXPERIENCE")
        experiencebar:RegisterEvent("PET_UI_UPDATE")
        experiencebar:RegisterEvent("PET_BAR_UPDATE")
    end

    if GW.Retail or GW.Mists then
        experiencebar:RegisterEvent("PET_BATTLE_LEVEL_CHANGED")
        experiencebar:RegisterEvent("PET_BATTLE_PET_CHANGED")
        experiencebar:RegisterEvent("PET_BATTLE_XP_CHANGED")
    end

    local isHonorBarHooked = false
    local function hookHonor()
        if not isHonorBarHooked then
            PVPQueueFrame.HonorInset.CasualPanel.HonorLevelDisplay:SetScript("OnMouseUp", function()
                if IsShiftKeyDown() then
                    if IsWatchingHonorAsXP() then
                        PlaySound(857)
                        SetWatchingHonorAsXP(false)
                    else
                        PlaySound(856)
                        SetWatchingHonorAsXP(true)
                    end

                    UpdateData(experiencebar)
                end
            end)

            PVPQueueFrame.HonorInset.CasualPanel.HonorLevelDisplay:HookScript("OnEnter", function()
                GameTooltip:AddLine(" ")
                GameTooltip:AddLine("|cffaaaaaa" .. L["Shift-Click to show as experience bar."] .. "|r")
                GameTooltip:Show()
            end)

            PVPQueueFrame.HonorInset.CasualPanel.HonorLevelDisplay:HookScript("OnLeave", function()
                GameTooltip:Hide()
            end)

            isHonorBarHooked = true
        end
    end

    if GW.Retail then
        if C_AddOns.IsAddOnLoaded("Blizzard_PVPUI") then
            hookHonor()
        else
            hooksecurefunc("UIParentLoadAddOn", function(addOnName)
                if addOnName == "Blizzard_PVPUI" then
                    hookHonor()
                end
            end)
        end
    end

    experiencebar.UpdateTooltip = xpbar_OnEnter
    experiencebar:SetScript("OnEnter", xpbar_OnEnter)
    experiencebar:SetScript(
        "OnLeave",
        function(self)
            GameTooltip_Hide()

            SetStatusBarHover(self.ExpBar, false)
            SetStatusBarHover(self.Rested, false)
            SetStatusBarHover(self.RepuBar, false)
            SetStatusBarHover(self.PetBar, false)
            SetStatusBarHover(self.AzeritBar, false)
        end
    )

    if GW.Retail then
        local guid = C_Housing.GetTrackedHouseGuid()
        if guid then
            C_Housing.GetCurrentHouseLevelFavor(guid)
        end
    end

    UpdateData(experiencebar)
end
GW.LoadXPBar = LoadXPBar
