-- TOG Tools — Addon Load Monitor (engine)
-- Provides per-addon memory, status, and load timing for the diagnostic tab.
--
-- Timing source (in priority order):
--   1. TOGToolsEarlyData — populated by the !TOGT companion addon, which loads
--      before every other addon (! sorts before all letters). Gives full coverage.
--   2. Own partial capture — this file registers ADDON_LOADED at file-load time
--      (~T in the alphabet), so only addons T–Z have timing if !TOGT is absent.
-- Memory and status are available for every installed addon regardless.

local _, addon = ...

local AddonLoad = {}
addon.modules["addonload"] = AddonLoad

-- TBC+ moved these globals into C_AddOns; Classic Era keeps the bare globals.
local _GetNumAddOns         = (C_AddOns and C_AddOns.GetNumAddOns)         or GetNumAddOns
local _GetAddOnInfo         = (C_AddOns and C_AddOns.GetAddOnInfo)         or GetAddOnInfo
local _IsAddOnLoaded        = (C_AddOns and C_AddOns.IsAddOnLoaded)        or IsAddOnLoaded
local _GetAddOnMemoryUsage  = (C_AddOns and C_AddOns.GetAddOnMemoryUsage)  or GetAddOnMemoryUsage
local _UpdateAddOnMemoryUsage = (C_AddOns and C_AddOns.UpdateAddOnMemoryUsage) or UpdateAddOnMemoryUsage

-- ---------------------------------------------------------------------------
-- Partial capture fallback — only used when !TOGT companion is not installed
-- ---------------------------------------------------------------------------
local _captureFrame = CreateFrame("Frame")
local _loadData     = {}   -- [addonName] = { order, offset }
local _captureOrder = 0
local _captureStart = nil
local _loginOffset  = nil

-- debugprofilestop() returns sub-millisecond precision; GetTime() is frame-locked,
-- which causes every ADDON_LOADED event in the same frame to share the same value.
_captureFrame:RegisterEvent("ADDON_LOADED")
_captureFrame:RegisterEvent("PLAYER_LOGIN")
_captureFrame:SetScript("OnEvent", function(_, event, arg1)
    if event == "ADDON_LOADED" then
        local t = debugprofilestop()
        _captureOrder = _captureOrder + 1
        if not _captureStart then _captureStart = t end
        _loadData[arg1] = { order = _captureOrder, offset = (t - _captureStart) / 1000 }
    elseif event == "PLAYER_LOGIN" then
        _loginOffset = _captureStart and ((debugprofilestop() - _captureStart) / 1000) or 0
        _UpdateAddOnMemoryUsage()
        _captureFrame:UnregisterAllEvents()
    end
end)

-- Returns the best available timing table and login offset.
-- Prefers TOGToolsEarlyData (full coverage) over our own partial capture.
local function getTimingSource()
    local early = TOGToolsEarlyData
    if early and early.startTime then
        return early.loadTimes, early.loginTime, true
    end
    return _loadData, _loginOffset, false
end

-- ---------------------------------------------------------------------------
-- Data API — called by AddonLoadTab when drawing/refreshing
-- ---------------------------------------------------------------------------

-- Status sort priority: lower = worse = sorts first when descending.
-- Failures (1-5), neutral (6-7), loaded (8).
local STATUS_PRIORITY = {
    ["Missing"]     = 1,
    ["Dep Missing"] = 2,
    ["Corrupt TOC"] = 3,
    ["Wrong Version"] = 4,
    ["Outdated"]    = 5,
    ["On Demand"]   = 6,
    ["Disabled"]    = 7,
    ["Loaded"]      = 8,
}

-- Neutral reasons: not a user error, not a loading failure.
local NEUTRAL_REASONS = {
    DISABLED     = true,
    DEMAND_LOADED = true,
}

local REASON_LABELS = {
    MISSING           = "Missing",
    DEP_MISSING       = "Dep Missing",
    DISABLED          = "Disabled",
    INTERFACE_VERSION = "Wrong Version",
    CORRUPT           = "Corrupt TOC",
    DEMAND_LOADED     = "On Demand",
    OUTDATED          = "Outdated",
}

-- Returns rows (array) and summary (table).
-- RefreshMemory() should be called first for fresh KB numbers.
function AddonLoad:GetData()
    local timingData, loginOffset, hasCompanion = getTimingSource()
    local rows   = {}
    local sumMem = 0
    local nLoaded, nFailed, nDisabled = 0, 0, 0

    for i = 1, _GetNumAddOns() do
        local name, title, _, loadable, reason = _GetAddOnInfo(i)
        if name then
            local isLoaded = _IsAddOnLoaded(name)
            local mem      = _GetAddOnMemoryUsage(name) or 0
            sumMem         = sumMem + mem

            local status, statusColor

            if isLoaded then
                status      = "Loaded"
                statusColor = "ff44ee44"
                nLoaded     = nLoaded + 1
            elseif not loadable then
                local r = reason or "FAILED"
                status = REASON_LABELS[r] or r
                if NEUTRAL_REASONS[r] then
                    statusColor = "ff888888"
                    nDisabled   = nDisabled + 1
                else
                    statusColor = "ffff5555"
                    nFailed     = nFailed + 1
                end
            else
                -- Loadable but not yet loaded = load-on-demand addon
                status      = "On Demand"
                statusColor = "ffaaaaaa"
                nDisabled   = nDisabled + 1
            end

            local capture = timingData[name]
            rows[#rows + 1] = {
                name         = name,
                title        = (title and title ~= "") and title or name,
                mem          = mem,
                status       = status,
                statusColor  = statusColor,
                statusPrio   = STATUS_PRIORITY[status] or 9,
                order        = capture and capture.order  or nil,
                offset       = capture and capture.offset or nil,
                loadTime     = nil,  -- filled in below
            }
        end
    end

    -- Compute per-addon load time = delta between consecutive ADDON_LOADED events.
    -- Sort timed rows by load order, then diff adjacent offsets.
    -- Row N's loadTime = offset[N] - offset[N-1] (how long addon N-1 held the thread).
    -- Row 1's loadTime = 0 since it defines the start.
    local timed = {}
    for _, row in ipairs(rows) do
        if row.order then timed[#timed + 1] = row end
    end
    table.sort(timed, function(a, b) return a.order < b.order end)
    for i, row in ipairs(timed) do
        if i == 1 then
            row.loadTime = 0
        else
            row.loadTime = row.offset - timed[i - 1].offset
        end
    end

    return rows, {
        total        = #rows,
        loaded       = nLoaded,
        failed       = nFailed,
        disabled     = nDisabled,
        totalMem     = sumMem,
        loginTime    = loginOffset,
        hasCompanion = hasCompanion,
    }
end

-- Re-snapshot memory from WoW's profiler.  Call before GetData() for fresh values.
function AddonLoad:RefreshMemory()
    _UpdateAddOnMemoryUsage()
end
