-- HDG_HT_W_NextRewards.lua  (A2)
-- Async: GetHouseLevelRewardsForLevel(level+1) → fires RECEIVED_HOUSE_LEVEL_REWARDS
-- with { level, rewards[] }. Cached per-level. Debounced to 0.3s.

local _, privateDB = ...
privateDB.HDG_HT = privateDB.HDG_HT or {}
privateDB.HDG_HT.Widgets = privateDB.HDG_HT.Widgets or {}
local W = { id = "nextRewards", title = "Current Level Rewards" }
privateDB.HDG_HT.Widgets.NextRewards = W

local Patterns = privateDB.HDG_HT.Patterns
local _cache  = {}             -- [level] = { rewards = {...} }
local _eventFrame = nil
-- Weak-value: orphaned widget instances (tab re-render) drop out of this set
-- via GC. Iterate with pairs(), not ipairs() (sparse). Monotonic key so
-- existing entries don't get overwritten.
local _instances     = setmetatable({}, { __mode = "v" })
local _nextInstanceK = 0

local function getMaxLevel()
    local maxLevel = 50
    if C_Housing and C_Housing.GetMaxHouseLevel then
        local ok, ml = pcall(C_Housing.GetMaxHouseLevel)
        if ok and ml then maxLevel = ml end
    end
    return maxLevel
end

local function getLevelInfo()
    -- Primary source: HDG_UI's per-active-house cache. Only populates when
    -- HOUSE_LEVEL_FAVOR_UPDATED fires for the player's currently-tracked
    -- house GUID — which never happens at max level (no XP gain).
    if privateDB.HDG_UI and privateDB.HDG_UI.GetCachedHouseData then
        local d = privateDB.HDG_UI.GetCachedHouseData()
        if d and d.level then return d.level, d.maxLevel end
    end

    -- Fallback: MultiHouse cache in HDG_DB.account.houses, populated by the
    -- PLAYER_HOUSE_LIST_UPDATED fan-out (calls GetCurrentHouseLevelFavor for
    -- every owned house). Take the highest level — that's the player's
    -- "main" house from the cockpit's POV.
    if HDG_DB and HDG_DB.account and HDG_DB.account.houses then
        local highest
        for _, h in pairs(HDG_DB.account.houses) do
            if h and h.level and (not highest or h.level > highest) then
                highest = h.level
            end
        end
        if highest then
            return highest, getMaxLevel()
        end
    end

    return nil, nil
end

local function ensureEventFrame()
    if _eventFrame then return end
    _eventFrame = CreateFrame("Frame")
    _eventFrame:RegisterEvent("RECEIVED_HOUSE_LEVEL_REWARDS")
    local debounce = nil
    _eventFrame:SetScript("OnEvent", function(_, _, level, rewards)
        if level and rewards then _cache[level] = { rewards = rewards } end
        if debounce then return end
        debounce = true
        C_Timer.After(0.3, function()
            debounce = nil
            for _, inst in pairs(_instances) do
                if inst.Refresh then
                    local snap = privateDB.HDG_HT.Aggregator and privateDB.HDG_HT.Aggregator.GetSnapshot()
                    inst:Refresh(snap)
                end
            end
        end)
    end)
end

local function fetchLevel(level)
    if _cache[level] then return _cache[level].rewards end
    if C_Housing and C_Housing.GetHouseLevelRewardsForLevel then
        -- AllowedWhenUntainted: throws if call stack is tainted (e.g. when
        -- Aggregator broadcasts inside another addon's event handler).
        pcall(C_Housing.GetHouseLevelRewardsForLevel, level)
    end
    return nil
end

function W.Create(parent, dims)
    ensureEventFrame()
    local self = {}
    self.pattern = Patterns.IconRowList(parent, dims, {
        rowHeight = dims.rowHeight or 28,
        iconSize  = dims.iconSize  or 24,
    })
    _nextInstanceK = _nextInstanceK + 1
    _instances[_nextInstanceK] = self

    -- Map valueType enum → friendly name + atlas, exactly as
    -- Blizzard_HousingDashboardHouseUpgrade.lua does:
    --   ValueTypeStrings[valueType] = HOUSING_DASHBOARD_REWARD_*_BUDGET (global string)
    --   ValueTypePortraits[valueType] = atlas name
    local function GetValueTypeMaps()
        if privateDB.HDG_HT._valueTypeStrings then
            return privateDB.HDG_HT._valueTypeStrings, privateDB.HDG_HT._valueTypePortraits
        end
        -- Skip the cache write entirely if the enum isn't available yet
        -- (early-load races): otherwise the empty-table cache would be
        -- pinned forever and the labels would never resolve.
        if not (Enum and Enum.HouseLevelRewardValueType) then
            return {}, {}
        end
        local strings, portraits = {}, {}
        local E = Enum.HouseLevelRewardValueType
        strings[E.InteriorDecor] = _G.HOUSING_DASHBOARD_REWARD_INTERIOR_BUDGET or "Interior Decor Placement Budget"
        strings[E.ExteriorDecor] = _G.HOUSING_DASHBOARD_REWARD_EXTERIOR_BUDGET or "Exterior Decor Placement Budget"
        strings[E.Rooms]         = _G.HOUSING_DASHBOARD_REWARD_ROOM_BUDGET     or "Room Placement Budget"
        strings[E.Fixtures]      = _G.HOUSING_DASHBOARD_REWARD_FIXTURE_BUDGET  or "Fixture Placement Budget"
        portraits[E.InteriorDecor] = "Reward_GenericBudget"
        portraits[E.ExteriorDecor] = "Reward_GenericBudget"
        portraits[E.Rooms]         = "Reward_GenericRoom"
        portraits[E.Fixtures]      = "house-fixture-budget-icon"
        privateDB.HDG_HT._valueTypeStrings   = strings
        privateDB.HDG_HT._valueTypePortraits = portraits
        return strings, portraits
    end

    -- Render a list of rewards. Mirrors HousingUpgradeFrameMixin:SetRewards():
    --   Value rewards   → [atlas] BudgetName / oldValue » newValue
    --   Object rewards  → [iconTexture] objectName
    local function buildRows(rewards, cSuccess, cAccent)
        local strings, portraits = GetValueTypeMaps()
        -- Resolve enum constant once per render. Falls back to bare 0 if
        -- Enum.HouseLevelRewardType isn't loaded yet (shouldn't happen post-
        -- PLAYER_LOGIN but defensive against early-load races).
        local VALUE_TYPE = (Enum and Enum.HouseLevelRewardType
                            and Enum.HouseLevelRewardType.Value) or 0
        local rows = {}
        for _, r in ipairs(rewards or {}) do
            if r.type == VALUE_TYPE then
                local label = strings[r.valueType] or ("Bonus " .. tostring(r.valueType or "?"))
                local atlas = portraits[r.valueType]
                local newVal = r.newValue or 0
                local oldVal = r.oldValue or 0
                local lineB
                if newVal ~= oldVal then
                    lineB = string.format("%s%s|r -> %s%s|r", cAccent,
                        tostring(oldVal), cSuccess, tostring(newVal))
                else
                    -- No-change rewards (e.g. Bonuses cap) just show the
                    -- current/single value.
                    lineB = string.format("%s%s|r", cAccent, tostring(newVal))
                end
                rows[#rows + 1] = {
                    icon      = atlas and false or "Interface\\Icons\\INV_Misc_QuestionMark",
                    iconAtlas = atlas,
                    lineA     = label,
                    lineB     = lineB,
                }
            else
                rows[#rows + 1] = {
                    icon  = r.iconTexture or "Interface\\Icons\\INV_Misc_QuestionMark",
                    lineA = r.objectName or "Reward",
                }
            end
        end
        return rows
    end

    function self:Refresh(_)
        local Theme = privateDB.HDG_HT.Theme
        local cAccent, cDim, cSuccess = Theme.C_Accent(), Theme.C_Dim(), Theme.C_Success()
        local level, maxLevel = getLevelInfo()
        if not level or not maxLevel then
            self.pattern:Update({ rows = {
                { icon = false, lineA = cDim .. "Loading..." .. "|r" }
            } })
            return
        end

        -- At max level: show what was unlocked at the FINAL level as a recap,
        -- prefixed with a "Max Level - earned" header chip. More celebratory
        -- than the previous "At max level" stub.
        if level >= maxLevel then
            local rewards = fetchLevel(maxLevel)
            if not rewards then
                self.pattern:Update({ rows = {
                    { icon = false, lineA = cDim .. "Loading rewards..." .. "|r" }
                }, headerChip = cAccent .. "Max Level" .. "|r" })
                return
            end
            local rows = buildRows(rewards, cSuccess, cAccent)
            if #rows == 0 then
                rows[1] = { icon = false,
                            lineA = string.format("%sLevel %d|r %s- nothing left to earn|r",
                                cAccent, maxLevel, cDim) }
            end
            self.pattern:Update({ rows = rows })
            return
        end

        local nextLvl = level + 1
        local rewards = fetchLevel(nextLvl)
        local rows
        if not rewards then
            rows = {{ icon = false,
                      lineA = string.format("Level %d unlocks: %sloading...|r", nextLvl, cDim) }}
        else
            rows = buildRows(rewards, cSuccess, cAccent)
        end
        self.pattern:Update({ rows = rows, headerChip = string.format("Lvl %d ->", nextLvl) })
    end

    function self:Resize(w, h) self.pattern:Resize(w, h) end
    return self
end

return W
