local _, WN = ...

local equipmentSlots = {
    { id = 1,  name = "HeadSlot", enchant = false },
    { id = 2,  name = "NeckSlot", enchant = false },
    { id = 3,  name = "ShoulderSlot", enchant = false },
    { id = 15, name = "BackSlot", enchant = true },
    { id = 5,  name = "ChestSlot", enchant = true },
    { id = 9,  name = "WristSlot", enchant = true },
    { id = 10, name = "HandsSlot", enchant = false },
    { id = 6,  name = "WaistSlot", enchant = false },
    { id = 7,  name = "LegsSlot", enchant = true },
    { id = 8,  name = "FeetSlot", enchant = true },
    { id = 11, name = "Finger0Slot", enchant = true },
    { id = 12, name = "Finger1Slot", enchant = true },
    { id = 13, name = "Trinket0Slot", enchant = false },
    { id = 14, name = "Trinket1Slot", enchant = false },
    { id = 16, name = "MainHandSlot", enchant = true },
    { id = 17, name = "SecondaryHandSlot", enchant = false, offhand = true },
}

local socketKeys = {
    "EMPTY_SOCKET_PRISMATIC", "EMPTY_SOCKET_RED", "EMPTY_SOCKET_BLUE", "EMPTY_SOCKET_YELLOW",
    "EMPTY_SOCKET_META", "EMPTY_SOCKET_COGWHEEL", "EMPTY_SOCKET_HYDRAULIC", "EMPTY_SOCKET_DOMINATION",
    "EMPTY_SOCKET_CYPHER", "EMPTY_SOCKET_TINKER",
}

local slotLocaleKeys = {
    HeadSlot = "SLOT_HEAD", NeckSlot = "SLOT_NECK", ShoulderSlot = "SLOT_SHOULDER", BackSlot = "SLOT_BACK", ChestSlot = "SLOT_CHEST",
    WristSlot = "SLOT_WRIST", HandsSlot = "SLOT_HANDS", WaistSlot = "SLOT_WAIST", LegsSlot = "SLOT_LEGS", FeetSlot = "SLOT_FEET",
    Finger0Slot = "SLOT_RING1", Finger1Slot = "SLOT_RING2", Trinket0Slot = "SLOT_TRINKET1", Trinket1Slot = "SLOT_TRINKET2",
    MainHandSlot = "SLOT_MAINHAND", SecondaryHandSlot = "SLOT_OFFHAND",
}

local function IsTwoHanded(itemLink)
    if not itemLink then return false end
    local getter = C_Item and C_Item.GetItemInfoInstant or GetItemInfoInstant
    if type(getter) ~= "function" then return false end
    local _, _, _, equipLoc = WN:SafeCall(getter, itemLink)
    return equipLoc == "INVTYPE_2HWEAPON" or equipLoc == "INVTYPE_RANGED" or equipLoc == "INVTYPE_RANGEDRIGHT"
end

local function GetItemLevel(itemLink)
    if not itemLink then return 0 end
    local getter = C_Item and C_Item.GetDetailedItemLevelInfo or GetDetailedItemLevelInfo
    if type(getter) ~= "function" then return 0 end
    return WN:SafeNumber(WN:SafeCall(getter, itemLink), 0) or 0
end

local function GetEnchantID(itemLink)
    if type(itemLink) ~= "string" then return 0 end
    local itemString = string.match(itemLink, "item:[^|]+")
    if not itemString then return 0 end
    local enchant = string.match(itemString, "item:%-?%d+:(%-?%d*)")
    return tonumber(enchant) or 0
end

local function CountEmptySockets(itemLink)
    if not itemLink then return 0 end
    local getter = C_Item and C_Item.GetItemStats or GetItemStats
    if type(getter) ~= "function" then return 0 end
    local stats = WN:SafeCall(getter, itemLink)
    if type(stats) ~= "table" then return 0 end
    local total = 0
    for _, key in ipairs(socketKeys) do
        total = total + (WN:SafeNumber(stats[key], 0) or 0)
    end
    return total
end

local function GetPlayerMaxLevel()
    if type(GetMaxLevelForPlayerExpansion) == "function" then
        local level = WN:SafeNumber(WN:SafeCall(GetMaxLevelForPlayerExpansion), nil)
        if level then return level end
    end
    if type(MAX_PLAYER_LEVEL) == "number" and MAX_PLAYER_LEVEL > 0 then return MAX_PLAYER_LEVEL end
    return 90
end

local function GetSlotLabel(slot)
    local key = slotLocaleKeys[slot.name]
    if key then
        local translated = WN:T(key)
        if type(translated) == "string" and translated ~= "" and translated ~= key then return translated end
    end
    return slot.name
end

function WN:GetCharacterSummary()
    local localizedClassName, classFile = UnitClass("player")
    local specName = self:T("UNKNOWN_SPEC")
    local specIcon = nil
    if type(GetSpecialization) == "function" and type(GetSpecializationInfo) == "function" then
        local specIndex = self:SafeNumber(self:SafeCall(GetSpecialization), nil)
        if specIndex then
            local _, found, _, foundIcon = self:SafeCall(GetSpecializationInfo, specIndex)
            specName = self:SafeString(found, specName)
            specIcon = self:SafeNumber(foundIcon, nil)
        end
    end

    local average, equipped = 0, 0
    if type(GetAverageItemLevel) == "function" then
        average, equipped = self:SafeCall(GetAverageItemLevel)
    end

    local versatility = 0
    if type(GetCombatRatingBonus) == "function" and CR_VERSATILITY_DAMAGE_DONE then
        versatility = self:SafeNumber(self:SafeCall(GetCombatRatingBonus, CR_VERSATILITY_DAMAGE_DONE), 0) or 0
    end

    local health = self:SafeNumber(self:SafeCall(UnitHealthMax, "player"), nil)
    local powerType = self:SafeNumber(self:SafeCall(UnitPowerType, "player"), 0) or 0
    local power = self:SafeNumber(self:SafeCall(UnitPowerMax, "player", powerType), nil)

    return {
        name = self:SafeString(UnitName("player"), "?"),
        realm = self:SafeString(GetRealmName and GetRealmName(), ""),
        className = self:SafeString(localizedClassName, ""),
        classFile = self:SafeString(classFile, ""),
        factionGroup = self:SafeString(self:SafeCall(UnitFactionGroup, "player"), ""),
        specName = specName,
        specIcon = specIcon,
        level = self:SafeNumber(UnitLevel("player"), 0) or 0,
        zone = self:SafeString(GetZoneText and GetZoneText(), ""),
        averageItemLevel = self:SafeNumber(average, 0) or 0,
        equippedItemLevel = self:SafeNumber(equipped, average) or 0,
        health = health,
        power = power,
        criticalStrike = self:SafeNumber(self:SafeCall(GetCritChance), nil),
        haste = self:SafeNumber(self:SafeCall(GetHaste), nil),
        mastery = self:SafeNumber(self:SafeCall(GetMasteryEffect), nil),
        versatility = versatility,
        mythicRating = self:GetMythicRating(),
    }
end

function WN:GetMythicRating()
    if C_ChallengeMode and type(C_ChallengeMode.GetOverallDungeonScore) == "function" then
        local score = self:SafeNumber(self:SafeCall(C_ChallengeMode.GetOverallDungeonScore), nil)
        if score then return score end
    end
    if C_PlayerInfo and type(C_PlayerInfo.GetPlayerMythicPlusRatingSummary) == "function" then
        local summary = self:SafeCall(C_PlayerInfo.GetPlayerMythicPlusRatingSummary, "player")
        if type(summary) == "table" then
            return self:SafeNumber(summary.currentSeasonScore, 0) or 0
        end
    end
    return 0
end

function WN:GetEquipmentReport()
    local report = {
        items = {}, required = 0, equipped = 0, missingItems = 0, missingEnchants = 0,
        emptySockets = 0, passedChecks = 0, totalChecks = 0, score = 0, complete = false,
        upgradeCandidates = {},
    }

    local maxLevel = GetPlayerMaxLevel()
    local playerLevel = self:SafeNumber(UnitLevel("player"), 0) or 0
    local checkEnchants = playerLevel >= maxLevel and (not self.db or self.db.settings.checkEnchants ~= false)
    local mainHand = GetInventoryItemLink("player", 16)
    local twoHanded = IsTwoHanded(mainHand)

    for _, slot in ipairs(equipmentSlots) do
        local optional = slot.offhand and twoHanded
        local itemLink = GetInventoryItemLink("player", slot.id)
        local itemName = nil
        local quality = nil
        if itemLink and type(GetItemInfo) == "function" then
            itemName, _, quality = self:SafeCall(GetItemInfo, itemLink)
        end

        local row = {
            slotID = slot.id,
            slotName = GetSlotLabel(slot),
            link = itemLink,
            name = self:SafeString(itemName, itemLink and self:T("ITEM") or self:T("EMPTY")),
            icon = GetInventoryItemTexture("player", slot.id),
            itemLevel = GetItemLevel(itemLink),
            quality = self:SafeNumber(quality, nil),
            optional = optional,
            missingEnchant = false,
            emptySockets = 0,
            status = "ok",
        }

        if not optional then
            report.required = report.required + 1
            report.totalChecks = report.totalChecks + 1
        end

        if itemLink then
            report.equipped = report.equipped + 1
            if not optional then report.passedChecks = report.passedChecks + 1 end

            if checkEnchants and slot.enchant then
                report.totalChecks = report.totalChecks + 1
                row.missingEnchant = GetEnchantID(itemLink) <= 0
                if row.missingEnchant then
                    report.missingEnchants = report.missingEnchants + 1
                    row.status = "warning"
                else
                    report.passedChecks = report.passedChecks + 1
                end
            end

            row.emptySockets = CountEmptySockets(itemLink)
            if row.emptySockets > 0 then
                report.emptySockets = report.emptySockets + row.emptySockets
                report.totalChecks = report.totalChecks + row.emptySockets
                row.status = "warning"
            end

            if not optional and row.itemLevel > 0 then
                report.upgradeCandidates[#report.upgradeCandidates + 1] = row
            end
        elseif optional then
            row.status = "optional"
        else
            report.missingItems = report.missingItems + 1
            row.status = "missing"
        end

        report.items[#report.items + 1] = row
    end

    table.sort(report.upgradeCandidates, function(a, b)
        if a.itemLevel == b.itemLevel then return a.slotID < b.slotID end
        return a.itemLevel < b.itemLevel
    end)
    while #report.upgradeCandidates > 3 do table.remove(report.upgradeCandidates) end

    if report.totalChecks > 0 then
        report.score = math.floor((report.passedChecks / report.totalChecks) * 100 + 0.5)
    end
    report.complete = report.missingItems == 0 and report.missingEnchants == 0 and report.emptySockets == 0
    return report
end

function WN:GetWeeklyProgress()
    local report = {
        groups = {}, list = {}, completedThresholds = 0, totalThresholds = 0,
        vaultAvailable = false,
    }

    if C_WeeklyRewards and type(C_WeeklyRewards.HasAvailableRewards) == "function" then
        report.vaultAvailable = self:SafeBoolean(self:SafeCall(C_WeeklyRewards.HasAvailableRewards), false) or false
    end
    if not C_WeeklyRewards or type(C_WeeklyRewards.GetActivities) ~= "function" then
        return report
    end

    local activities = self:SafeCall(C_WeeklyRewards.GetActivities)
    if type(activities) ~= "table" then return report end

    local enum = Enum and Enum.WeeklyRewardChestThresholdType or {}
    local keys = {}
    if enum.Activities ~= nil then keys[enum.Activities] = "MYTHIC_PLUS" end
    if enum.Raid ~= nil then keys[enum.Raid] = "RAID" end
    if enum.World ~= nil then keys[enum.World] = "DELVES_WORLD" end
    if enum.RankedPvP ~= nil then keys[enum.RankedPvP] = "PVP" end

    for index, activity in ipairs(activities) do
        if type(activity) == "table" then
            local progress = self:SafeNumber(activity.progress, 0) or 0
            local threshold = self:SafeNumber(activity.threshold, 0) or 0
            local activityType = self:SafeNumber(activity.type, -1) or -1
            local key = keys[activityType] or "GREAT_VAULT"
            local row = {
                type = activityType,
                key = key,
                index = self:SafeNumber(activity.index, index) or index,
                progress = progress,
                threshold = threshold,
                level = self:SafeNumber(activity.level, 0) or 0,
                complete = threshold > 0 and progress >= threshold,
            }
            report.list[#report.list + 1] = row
            report.totalThresholds = report.totalThresholds + 1
            if row.complete then report.completedThresholds = report.completedThresholds + 1 end

            local group = report.groups[key]
            if not group then
                group = { key = key, progress = progress, threshold = threshold, completed = 0, total = 0, maxLevel = 0 }
                report.groups[key] = group
            end
            group.progress = math.max(group.progress, progress)
            group.threshold = math.max(group.threshold, threshold)
            group.total = group.total + 1
            if row.complete then group.completed = group.completed + 1 end
            group.maxLevel = math.max(group.maxLevel, row.level)
        end
    end

    table.sort(report.list, function(a, b)
        if a.type == b.type then return a.index < b.index end
        return a.type < b.type
    end)
    return report
end

function WN:GetWatchedReputation()
    if C_Reputation and type(C_Reputation.GetWatchedFactionData) == "function" then
        local data = self:SafeCall(C_Reputation.GetWatchedFactionData)
        if type(data) == "table" then
            local current = self:SafeNumber(data.currentStanding, 0) or 0
            local low = self:SafeNumber(data.currentReactionThreshold, 0) or 0
            local high = self:SafeNumber(data.nextReactionThreshold, 0) or 0
            return {
                name = self:SafeString(data.name, self:T("REPUTATION")),
                current = math.max(0, current - low),
                maximum = math.max(0, high - low),
            }
        end
    end
    return nil
end

function WN:GetCurrencyReport()
    local result = { money = self:SafeNumber(self:SafeCall(GetMoney), 0) or 0, currencies = {} }
    if not C_CurrencyInfo or type(C_CurrencyInfo.GetBackpackCurrencyInfo) ~= "function" then return result end
    for index = 1, 20 do
        local info = self:SafeCall(C_CurrencyInfo.GetBackpackCurrencyInfo, index)
        if type(info) ~= "table" then break end
        local name = self:SafeString(info.name, nil)
        if name then
            result.currencies[#result.currencies + 1] = {
                name = name,
                quantity = self:SafeNumber(info.quantity, 0) or 0,
                maxQuantity = self:SafeNumber(info.maxQuantity, 0) or 0,
                icon = info.iconFileID,
            }
            if #result.currencies >= 5 then break end
        end
    end
    return result
end

function WN:FormatMoney(copper)
    copper = self:SafeNumber(copper, 0) or 0
    if type(GetMoneyString) == "function" then
        local value = self:SafeCall(GetMoneyString, copper, true)
        if type(value) == "string" then return value end
    end
    return string.format("%.0f", copper / 10000)
end

function WN:GetSavedInstancesReport()
    local result = {}
    if type(GetNumSavedInstances) ~= "function" or type(GetSavedInstanceInfo) ~= "function" then return result end
    local count = self:SafeNumber(self:SafeCall(GetNumSavedInstances), 0) or 0
    for index = 1, count do
        local name, _, reset, difficultyID, locked, extended, _, isRaid, maxPlayers, difficultyName, encounters, completed = self:SafeCall(GetSavedInstanceInfo, index)
        if self:SafeString(name, nil) and (locked == true or isRaid == true) then
            result[#result + 1] = {
                name = self:SafeString(name, self:T("INSTANCES")),
                reset = self:SafeNumber(reset, 0) or 0,
                difficultyID = self:SafeNumber(difficultyID, 0) or 0,
                difficultyName = self:SafeString(difficultyName, ""),
                locked = locked == true,
                extended = extended == true,
                isRaid = isRaid == true,
                maxPlayers = self:SafeNumber(maxPlayers, 0) or 0,
                encounters = self:SafeNumber(encounters, 0) or 0,
                completed = self:SafeNumber(completed, 0) or 0,
            }
        end
    end
    table.sort(result, function(a, b)
        if a.isRaid ~= b.isRaid then return a.isRaid end
        return a.reset < b.reset
    end)
    return result
end

function WN:GetProfessionReport()
    local result = { professions = {} }
    if type(GetProfessions) ~= "function" or type(GetProfessionInfo) ~= "function" then return result end
    local p1, p2 = self:SafeCall(GetProfessions)
    for _, professionIndex in pairs({ p1, p2 }) do
        if professionIndex then
            local name, icon, skill, maxSkill, _, _, skillLine, modifier = self:SafeCall(GetProfessionInfo, professionIndex)
            result.professions[#result.professions + 1] = {
                index = professionIndex,
                name = self:SafeString(name, self:T("PROFESSIONS")),
                icon = icon,
                skill = self:SafeNumber(skill, 0) or 0,
                maxSkill = self:SafeNumber(maxSkill, 0) or 0,
                skillLine = self:SafeNumber(skillLine, 0) or 0,
                modifier = self:SafeNumber(modifier, 0) or 0,
            }
        end
    end
    return result
end

function WN:GetCollectionReport(force)
    local now = GetTime and GetTime() or 0
    if not force and self.collectionCache and self.collectionCacheAt and now - self.collectionCacheAt < 300 then
        return self.collectionCache
    end
    local result = {
        mounts = { collected = 0, total = 0 }, pets = { collected = 0, total = 0 },
        toys = { collected = 0, total = 0 }, appearances = { collected = 0, total = 0 },
        achievementPoints = 0,
    }

    if C_MountJournal and type(C_MountJournal.GetMountIDs) == "function" and type(C_MountJournal.GetMountInfoByID) == "function" then
        local ids = self:SafeCall(C_MountJournal.GetMountIDs)
        if type(ids) == "table" then
            for _, mountID in ipairs(ids) do
                local _, _, _, _, _, _, _, _, _, shouldHide, collected = self:SafeCall(C_MountJournal.GetMountInfoByID, mountID)
                if shouldHide ~= true then
                    result.mounts.total = result.mounts.total + 1
                    if collected == true then result.mounts.collected = result.mounts.collected + 1 end
                end
            end
        end
    elseif C_MountJournal and type(C_MountJournal.GetNumMounts) == "function" then
        result.mounts.total = self:SafeNumber(self:SafeCall(C_MountJournal.GetNumMounts), 0) or 0
    end

    if C_PetJournal and type(C_PetJournal.GetNumPets) == "function" then
        local total, owned = self:SafeCall(C_PetJournal.GetNumPets)
        result.pets.total = self:SafeNumber(total, 0) or 0
        result.pets.collected = self:SafeNumber(owned, 0) or 0
    end

    if C_ToyBox then
        if type(C_ToyBox.GetNumToys) == "function" then
            result.toys.total = self:SafeNumber(self:SafeCall(C_ToyBox.GetNumToys), 0) or 0
        end
        if type(C_ToyBox.GetNumLearnedDisplayedToys) == "function" then
            result.toys.collected = self:SafeNumber(self:SafeCall(C_ToyBox.GetNumLearnedDisplayedToys), 0) or 0
        end
    end

    if C_TransmogCollection and type(C_TransmogCollection.GetCategoryCollectedCount) == "function" and type(C_TransmogCollection.GetCategoryTotal) == "function" then
        for category = 1, 29 do
            local collected = self:SafeNumber(self:SafeCall(C_TransmogCollection.GetCategoryCollectedCount, category), 0) or 0
            local total = self:SafeNumber(self:SafeCall(C_TransmogCollection.GetCategoryTotal, category), 0) or 0
            result.appearances.collected = result.appearances.collected + collected
            result.appearances.total = result.appearances.total + total
        end
    end

    if type(GetTotalAchievementPoints) == "function" then
        result.achievementPoints = self:SafeNumber(self:SafeCall(GetTotalAchievementPoints), 0) or 0
    end
    self.collectionCache = result
    self.collectionCacheAt = now
    return result
end

local function GetAddonCount()
    if C_AddOns and type(C_AddOns.GetNumAddOns) == "function" then
        return WN:SafeNumber(WN:SafeCall(C_AddOns.GetNumAddOns), 0) or 0
    end
    if type(GetNumAddOns) == "function" then return WN:SafeNumber(WN:SafeCall(GetNumAddOns), 0) or 0 end
    return 0
end

local function GetAddonInfo(index)
    local getter = C_AddOns and C_AddOns.GetAddOnInfo or GetAddOnInfo
    if type(getter) ~= "function" then return nil end
    local a, b, c, d, e = WN:SafeCall(getter, index)
    if type(a) == "table" then
        return { name = a.name, title = a.title, notes = a.notes, loadable = a.loadable, reason = a.reason }
    end
    if not a then return nil end
    return { name = a, title = b, notes = c, loadable = d, reason = e }
end

function WN:FormatMemory(kb)
    kb = self:SafeNumber(kb, 0) or 0
    if kb >= 1024 then return string.format("%.1f MB", kb / 1024) end
    return string.format("%.0f KB", kb)
end

function WN:GetAddonReport(force)
    local now = GetTime and GetTime() or 0
    if not force and self.addonReportCache and self.addonReportCacheAt and now - self.addonReportCacheAt < 60 then
        return self.addonReportCache
    end
    if type(UpdateAddOnMemoryUsage) == "function" then self:SafeCall(UpdateAddOnMemoryUsage) end
    local result = { addons = {}, total = 0, loaded = 0, memory = 0 }
    for index = 1, GetAddonCount() do
        local info = GetAddonInfo(index)
        if info and info.name then
            local loaded = false
            local isLoaded = C_AddOns and C_AddOns.IsAddOnLoaded or IsAddOnLoaded
            if type(isLoaded) == "function" then loaded = self:SafeBoolean(self:SafeCall(isLoaded, info.name), false) or false end
            local enabled = info.loadable ~= false
            if C_AddOns and type(C_AddOns.GetAddOnEnableState) == "function" then
                local state = self:SafeNumber(self:SafeCall(C_AddOns.GetAddOnEnableState, info.name, UnitName("player")), nil)
                if state ~= nil then enabled = state > 0 end
            end
            local memory = 0
            if loaded and type(GetAddOnMemoryUsage) == "function" then memory = self:SafeNumber(self:SafeCall(GetAddOnMemoryUsage, index), 0) or 0 end
            local version, category = nil, ""
            local meta = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetadata
            if type(meta) == "function" then
                version = self:SafeString(self:SafeCall(meta, info.name, "Version"), "")
                category = self:SafeString(self:SafeCall(meta, info.name, "X-Category"), "")
                if category == "" then category = self:SafeString(self:SafeCall(meta, info.name, "Category"), "") end
            end
            result.total = result.total + 1
            if loaded then result.loaded = result.loaded + 1 end
            result.memory = result.memory + memory
            result.addons[#result.addons + 1] = {
                index = index, name = info.name, title = self:SafeString(info.title, info.name), version = version or "",
                notes = self:SafeString(info.notes, ""), category = category or "", loaded = loaded, enabled = enabled, memory = memory,
            }
        end
    end
    table.sort(result.addons, function(a, b)
        if a.loaded ~= b.loaded then return a.loaded end
        return string.lower(a.title) < string.lower(b.title)
    end)
    self.addonReportCache = result
    self.addonReportCacheAt = now
    return result
end

function WN:IsAddonCPUProfilingEnabled()
    if type(GetCVarBool) == "function" then
        local value = self:SafeCall(GetCVarBool, "scriptProfile")
        return value == true or value == 1
    end
    return false
end

function WN:UpdatePerformanceSnapshot(interval, includeMemory, includeCPU)
    interval = math.max(self:SafeNumber(interval, 1) or 1, 0.1)
    local fps = self:SafeNumber(self:SafeCall(GetFramerate), 0) or 0
    local _, _, home, world = self:SafeCall(GetNetStats)
    local p = self.performance or {}
    p.fps = fps
    p.frameTime = fps > 0 and 1000 / fps or 0
    p.home = self:SafeNumber(home, 0) or 0
    p.world = self:SafeNumber(world, 0) or 0

    local now = GetTime and GetTime() or 0
    if includeMemory == true and (not p.lastMemory or now - p.lastMemory >= 60) then
        if type(UpdateAddOnMemoryUsage) == "function" then self:SafeCall(UpdateAddOnMemoryUsage) end
        local total = 0
        for index = 1, GetAddonCount() do
            if type(GetAddOnMemoryUsage) == "function" then total = total + (self:SafeNumber(self:SafeCall(GetAddOnMemoryUsage, index), 0) or 0) end
        end
        p.memory = total
        p.lastMemory = now
    end

    p.cpuEnabled = self:IsAddonCPUProfilingEnabled()
    if includeCPU == true and p.cpuEnabled and type(UpdateAddOnCPUUsage) == "function" and type(GetAddOnCPUUsage) == "function" then
        if not p.lastCPURefresh or now - p.lastCPURefresh >= 15 then
            self:SafeCall(UpdateAddOnCPUUsage)
            local total = 0
            for index = 1, GetAddonCount() do total = total + (self:SafeNumber(self:SafeCall(GetAddOnCPUUsage, index), 0) or 0) end
            if p.lastCPU and p.lastCPUSampleAt then
                local elapsed = math.max(0.1, now - p.lastCPUSampleAt)
                p.cpuPercent = math.max(0, (total - p.lastCPU) / (elapsed * 1000) * 100)
            end
            p.lastCPU = total
            p.lastCPUSampleAt = now
            p.lastCPURefresh = now
        end
    elseif not p.cpuEnabled then
        p.cpuPercent = nil
        p.lastCPU = nil
        p.lastCPUSampleAt = nil
        p.lastCPURefresh = nil
    end
    self.performance = p
    return p
end

function WN:GetResetInfo()
    local daily, weekly = 0, 0
    if C_DateAndTime and type(C_DateAndTime.GetSecondsUntilDailyReset) == "function" then
        daily = self:SafeNumber(self:SafeCall(C_DateAndTime.GetSecondsUntilDailyReset), 0) or 0
    end
    if C_DateAndTime and type(C_DateAndTime.GetSecondsUntilWeeklyReset) == "function" then
        weekly = self:SafeNumber(self:SafeCall(C_DateAndTime.GetSecondsUntilWeeklyReset), 0) or 0
    end
    return { daily = daily, weekly = weekly }
end

function WN:GetQuestCount()
    if C_QuestLog and type(C_QuestLog.GetNumQuestLogEntries) == "function" then
        local _, quests = self:SafeCall(C_QuestLog.GetNumQuestLogEntries)
        return self:SafeNumber(quests, 0) or 0
    end
    return 0
end

function WN:GetOverviewData()
    return {
        character = self:GetCharacterSummary(),
        equipment = self:GetEquipmentReport(),
        weekly = self:GetWeeklyProgress(),
        currencies = self:GetCurrencyReport(),
        instances = self:GetSavedInstancesReport(),
        professions = self:GetProfessionReport(),
        collections = self:GetCollectionReport(),
        reputation = self:GetWatchedReputation(),
        resets = self:GetResetInfo(),
        questCount = self:GetQuestCount(),
    }
end
