local _, WN = ...

local function Line(parent, y, icon)
    local row = CreateFrame("Frame", nil, parent)
    row:SetPoint("TOPLEFT", 10, y); row:SetPoint("TOPRIGHT", -10, y); row:SetHeight(30)
    row.icon = row:CreateTexture(nil,"ARTWORK"); row.icon:SetSize(18,18); row.icon:SetPoint("LEFT",0,0); row.icon:SetTexture(icon or WN.icons.info)
    row.label = WN:CreateText(row,10,WN.colors.text,"LEFT"); row.label:SetPoint("LEFT",26,0); row.label:SetPoint("RIGHT",-90,0)
    row.value = WN:CreateText(row,9,WN.colors.muted,"RIGHT"); row.value:SetPoint("RIGHT",0,0); row.value:SetWidth(86)
    return row
end

local function ClickRow(parent, width, height)
    local row = WN:CreateIconRow(parent,width,height)
    row:RegisterForClicks("LeftButtonUp","RightButtonUp")
    return row
end

function WN:CreatePlannerSection(parent)
    local page=CreateFrame("Frame",nil,parent); page:SetAllPoints(); self.sections.planner=page; self:CreateSectionHeader(page)
    page.headerTitle:SetText(self:T("SMART_PLANNER")); page.headerSub:SetText(self:T("SMART_PLANNER_SUB"))

    page.plan=self:CreatePanel(page,self:T("THIS_WEEK"),500,430); page.plan:SetPoint("TOPLEFT",0,-48); page.plan.rows={}
    for i=1,9 do
        local row=CreateFrame("Button",nil,page.plan,"BackdropTemplate"); row:SetSize(478,40); row:SetPoint("TOPLEFT",10,-40-(i-1)*43); self.ApplyBackdrop(row,self.colors.panel2,self.colors.borderSoft)
        row.icon=row:CreateTexture(nil,"ARTWORK"); row.icon:SetSize(20,20); row.icon:SetPoint("LEFT",8,0); row.icon:SetTexture(self.icons.check)
        row.label=self:CreateText(row,10,self.colors.text,"LEFT"); row.label:SetPoint("LEFT",36,6); row.label:SetPoint("RIGHT",-72,6)
        row.meta=self:CreateText(row,8,self.colors.muted,"LEFT"); row.meta:SetPoint("LEFT",36,-9); row.meta:SetPoint("RIGHT",-72,-9)
        row.value=self:CreateText(row,8,self.colors.accent,"RIGHT"); row.value:SetPoint("RIGHT",-8,0); row.value:SetWidth(62)
        row:SetScript("OnEnter",function(r) r:SetBackdropColor(unpack(WN.colors.panelHover)) end); row:SetScript("OnLeave",function(r) r:SetBackdropColor(unpack(WN.colors.panel2)) end)
        row:SetScript("OnClick",function(r) WN.selectedPlanIndex=r.index; WN:UpdatePlannerDetails() end)
        page.plan.rows[i]=row
    end

    page.details=self:CreatePanel(page,self:T("REWARD"),352,205); page.details:SetPoint("TOPLEFT",page.plan,"TOPRIGHT",10,0)
    page.details.titleText=self:CreateText(page.details,11,self.colors.text,"LEFT"); page.details.titleText:SetPoint("TOPLEFT",12,-42); page.details.titleText:SetPoint("RIGHT",-12,0); page.details.titleText:SetHeight(34)
    page.details.why=Line(page.details,-86,self.icons.info); page.details.where=Line(page.details,-120,self.icons.map); page.details.reward=Line(page.details,-154,self.icons.vault)
    page.details.why.label:SetText(self:T("REASON")); page.details.where.label:SetText(self:T("WHERE")); page.details.reward.label:SetText(self:T("REWARD"))
    page.details.open=self:CreateButton(page.details,110,28,self:T("OPEN_ACTION"),function() local task=WN.currentPlan and WN.currentPlan[WN.selectedPlanIndex or 1]; if task then WN:ExecutePlanAction(task.action) end end); page.details.open:SetPoint("BOTTOMRIGHT",-10,10)

    page.custom=self:CreatePanel(page,self:T("CUSTOM_TASKS"),352,215); page.custom:SetPoint("TOPLEFT",page.details,"BOTTOMLEFT",0,-10); page.custom.rows={}
    for i=1,4 do
        local row=CreateFrame("Button",nil,page.custom,"BackdropTemplate"); row:SetSize(330,31); row:SetPoint("TOPLEFT",10,-40-(i-1)*34); self.ApplyBackdrop(row,self.colors.panel2,self.colors.borderSoft); row:RegisterForClicks("LeftButtonUp","RightButtonUp")
        row.check=row:CreateTexture(nil,"ARTWORK"); row.check:SetSize(15,15); row.check:SetPoint("LEFT",7,0); row.check:SetTexture(self.icons.check)
        row.label=self:CreateText(row,9,self.colors.text,"LEFT"); row.label:SetPoint("LEFT",28,0); row.label:SetPoint("RIGHT",-8,0)
        row:SetScript("OnClick",function(r,button) if not r.taskID then return end; if button=="RightButton" then WN:DeleteCustomTask(r.taskID) else WN:ToggleCustomTask(r.taskID) end end)
        page.custom.rows[i]=row
    end
    page.custom.offset=0
    page.custom.prev=self:CreateButton(page.custom,24,20,"‹",function() page.custom.offset=math.max(0,(page.custom.offset or 0)-4); WN:UpdatePlannerSection() end); page.custom.prev:SetPoint("TOPRIGHT",-38,-8)
    page.custom.next=self:CreateButton(page.custom,24,20,"›",function() page.custom.offset=(page.custom.offset or 0)+4; WN:UpdatePlannerSection() end); page.custom.next:SetPoint("TOPRIGHT",-10,-8)
    page.custom.add=self:CreateButton(page.custom,100,28,self:T("ADD_TASK"),function() WN:OpenTaskDialog() end); page.custom.add:SetPoint("BOTTOMLEFT",10,9)
    page.custom.preset=self:CreateButton(page.custom,102,28,self:T("TASK_MPLUS"),function() WN:AddCustomTask(WN:T("TASK_MPLUS")) end); page.custom.preset:SetPoint("LEFT",page.custom.add,"RIGHT",7,0)
    page.custom.preset2=self:CreateButton(page.custom,102,28,self:T("TASK_RAID"),function() WN:AddCustomTask(WN:T("TASK_RAID")) end); page.custom.preset2:SetPoint("LEFT",page.custom.preset,"RIGHT",7,0)
end

function WN:UpdatePlannerDetails()
    local page=self.sections.planner; if not page then return end
    local task=self.currentPlan and self.currentPlan[self.selectedPlanIndex or 1]
    if not task then page.details.titleText:SetText(self:T("NO_DATA")); return end
    page.details.titleText:SetText(task.label or "")
    page.details.why.value:SetText(task.why or self:T("NA")); page.details.why.value:SetWidth(235)
    page.details.where.value:SetText(task.where or self:T("NA")); page.details.where.value:SetWidth(235)
    page.details.reward.value:SetText(task.reward or self:T("NA")); page.details.reward.value:SetWidth(235)
    page.details.open:SetShown(task.action ~= nil)
end

function WN:UpdatePlannerSection(data)
    local page=self.sections.planner; if not page then return end
    page.headerTitle:SetText(self:T("SMART_PLANNER")); page.headerSub:SetText(self:T("SMART_PLANNER_SUB")); page.plan.title:SetText(self:T("TODAY").." / "..self:T("THIS_WEEK")); page.details.title:SetText(self:T("REWARD")); page.custom.title:SetText(self:T("CUSTOM_TASKS"))
    local plan=self:GetSmartPlan(data); self.currentPlan=plan; if not self.selectedPlanIndex or self.selectedPlanIndex>#plan then self.selectedPlanIndex=1 end
    for i,row in ipairs(page.plan.rows) do
        local task=plan[i]
        if task then
            row:Show(); row.index=i; row.label:SetText(task.label); row.meta:SetText((task.where or "").."   •   "..(task.reward or "")); row.value:SetText(task.priority>=90 and self:T("HIGH") or task.priority>=50 and self:T("MEDIUM") or self:T("LOW")); row.icon:SetTexture(task.done and self.icons.check or self.icons.info); row.icon:SetVertexColor(unpack(task.done and self.colors.green or task.priority>=90 and self.colors.yellow or self.colors.accent))
            row:SetBackdropBorderColor(unpack(i==(self.selectedPlanIndex or 1) and self.colors.accentDim or self.colors.borderSoft))
        else row:Hide() end
    end
    self:UpdatePlannerDetails()
    local tasks=self.db.customTasks or {}; local maxOffset=math.max(0,(math.ceil(#tasks/4)-1)*4); page.custom.offset=math.min(math.max(0,page.custom.offset or 0),maxOffset)
    page.custom.prev:SetEnabled(page.custom.offset>0); page.custom.next:SetEnabled(page.custom.offset+4<#tasks)
    for i,row in ipairs(page.custom.rows) do
        local task=tasks[page.custom.offset+i]
        if task then row:Show(); row.taskID=task.id; row.label:SetText(task.text); row.check:SetShown(task.done==true); row.label:SetTextColor(unpack(task.done and self.colors.muted or self.colors.text)) else row:Hide(); row.taskID=nil end
    end
    page.custom.add.label:SetText(self:T("ADD_TASK")); page.custom.preset.label:SetText(self:T("TASK_MPLUS")); page.custom.preset2.label:SetText(self:T("TASK_RAID"))
end

function WN:CreateEndgameSection(parent)
    local page=CreateFrame("Frame",nil,parent); page:SetAllPoints(); self.sections.endgame=page; self:CreateSectionHeader(page); page.mode="mplus"
    page.tabs={}
    local tabs={{"MPLUS_CENTER","mplus"},{"RAID_CENTER","raid"},{"DELVES_CENTER","delves"}}
    for i,def in ipairs(tabs) do
        local b=self:CreateButton(page,150,30,self:T(def[1]),function() page.mode=def[2]; WN:UpdateEndgameSection() end); b:SetPoint("TOPLEFT",(i-1)*158,-48); b.mode=def[2]; b.textKey=def[1]; page.tabs[def[2]]=b
    end

    page.mplus=self:CreatePanel(page,self:T("MPLUS_CENTER"),862,392); page.mplus:SetPoint("TOPLEFT",0,-88)
    page.mplus.summary=self:CreateText(page.mplus,10,self.colors.accent,"LEFT"); page.mplus.summary:SetPoint("TOPLEFT",12,-40); page.mplus.summary:SetPoint("RIGHT",-12,0)
    page.mplus.affixes=self:CreateText(page.mplus,9,self.colors.muted,"LEFT"); page.mplus.affixes:SetPoint("TOPLEFT",12,-62); page.mplus.affixes:SetPoint("RIGHT",-12,0)
    page.mplus.rows={}
    for i=1,8 do
        local col=(i-1)%2; local rr=math.floor((i-1)/2); local row=ClickRow(page.mplus,410,62); row:SetPoint("TOPLEFT",12+col*420,-92-rr*68); row.right:SetWidth(105); page.mplus.rows[i]=row
    end

    page.raid=self:CreatePanel(page,self:T("RAID_CENTER"),862,392); page.raid:SetPoint("TOPLEFT",0,-88); page.raid.rows={}
    for i=1,5 do
        local row=ClickRow(page.raid,410,48); row:SetPoint("TOPLEFT",12,-42-(i-1)*53); row.index=i; row:SetScript("OnClick",function(r) WN.selectedRaidIndex=r.index; WN:UpdateEndgameSection() end); page.raid.rows[i]=row
    end
    page.raid.bossTitle=self:CreateText(page.raid,12,self.colors.text,"LEFT"); page.raid.bossTitle:SetPoint("TOPLEFT",442,-44); page.raid.bossTitle:SetText(self:T("BOSSES")); page.raid.bossRows={}
    for i=1,10 do local row=Line(page.raid,-70-(i-1)*29,self.icons.raid); row:ClearAllPoints(); row:SetPoint("TOPLEFT",442,-70-(i-1)*29); row:SetPoint("TOPRIGHT",-12,-70-(i-1)*29); page.raid.bossRows[i]=row end

    page.delves=self:CreatePanel(page,self:T("DELVES_CENTER"),862,392); page.delves:SetPoint("TOPLEFT",0,-88); page.delves.cards={}
    local defs={{"DELVE_SEASON","season"},{"ACTIVE_TIER","tier"},{"ACTIVE_DELVE","active"},{"REWARD_ELIGIBLE","reward"}}
    for i,def in ipairs(defs) do local card=self:CreatePanel(page.delves,self:T(def[1]),198,92); card:SetPoint("TOPLEFT",12+(i-1)*208,-42); card.value=self:CreateText(card,20,self.colors.text,"CENTER"); card.value:SetPoint("CENTER",0,-10); page.delves.cards[def[2]]=card end
    page.delves.weekly=self:CreatePanel(page.delves,self:T("WEEKLY_WORLD"),406,190); page.delves.weekly:SetPoint("TOPLEFT",12,-148); page.delves.weekly.bar=self:CreateProgressBar(page.delves.weekly,374,16); page.delves.weekly.bar:SetPoint("TOPLEFT",14,-54); page.delves.weekly.value=self:CreateText(page.delves.weekly,11,self.colors.text,"LEFT"); page.delves.weekly.value:SetPoint("TOPLEFT",14,-84)
    page.delves.locations=self:CreatePanel(page.delves,self:T("SEASON_DELVES"),406,190); page.delves.locations:SetPoint("TOPLEFT",430,-148); page.delves.locations.text=self:CreateText(page.delves.locations,10,self.colors.text,"LEFT"); page.delves.locations.text:SetPoint("TOPLEFT",14,-44); page.delves.locations.text:SetPoint("RIGHT",-14,0); page.delves.locations.text:SetHeight(72); page.delves.locations.note=self:CreateText(page.delves.locations,8,self.colors.muted,"LEFT"); page.delves.locations.note:SetPoint("BOTTOMLEFT",14,14); page.delves.locations.note:SetPoint("RIGHT",-14,0); page.delves.locations.note:SetHeight(44)
end

function WN:UpdateEndgameSection(data)
    local page=self.sections.endgame; if not page then return end
    data=data or self.cachedOverview or self:GetOverviewData(); page.headerTitle:SetText(self:T("ENDGAME_TITLE")); page.headerSub:SetText(self:T("ENDGAME_SUB"))
    for mode,b in pairs(page.tabs) do b:SetBackdropBorderColor(unpack(page.mode==mode and self.colors.accent or self.colors.borderSoft)); b.label:SetText(self:T(b.textKey)) end
    page.mplus:SetShown(page.mode=="mplus"); page.raid:SetShown(page.mode=="raid"); page.delves:SetShown(page.mode=="delves")
    if page.mode=="mplus" then
        local report=self:GetMythicPlusReport(); local keyText=report.ownedKey and (report.ownedKey.name.." +"..(report.ownedKey.level or 0)) or self:T("NA")
        page.mplus.summary:SetText(self:T("SEASON_SCORE")..": "..self:FormatNumber(report.score).."   •   "..self:T("KEYSTONE")..": "..keyText)
        local aff={}; for _,a in ipairs(report.affixes) do aff[#aff+1]=a.name end; page.mplus.affixes:SetText(self:T("AFFIXES")..": "..(#aff>0 and table.concat(aff," • ") or self:T("NA")))
        for i,row in ipairs(page.mplus.rows) do local m=report.maps[i]; if m then row:Show(); row.icon:SetTexture(m.icon or self.icons.mythic); row.name:SetText(m.name); row.meta:SetText(self:T("BEST_KEY")..": "..(m.bestLevel>0 and ("+"..m.bestLevel) or self:T("NA")).."   •   "..self:T("THIS_WEEK_SHORT")..": "..(m.weeklyLevel>0 and ("+"..m.weeklyLevel) or self:T("NA"))); row.right:SetText(m.bestScore>0 and self:FormatNumber(m.bestScore) or (m.timeLimit>0 and self:FormatDuration(m.timeLimit) or "")); row:SetScript("OnClick",function() WN:OpenTool("group") end) else row:Hide() end end
    elseif page.mode=="raid" then
        local report=self:GetRaidReport(); if not self.selectedRaidIndex or self.selectedRaidIndex>#report.lockouts then self.selectedRaidIndex=1 end
        for i,row in ipairs(page.raid.rows) do local r=report.lockouts[i]; if r then row:Show(); row.icon:SetTexture(self.icons.raid); row.name:SetText(r.name); row.meta:SetText(r.difficultyName); row.right:SetText(r.completed.."/"..r.encounters); row:SetBackdropBorderColor(unpack(i==self.selectedRaidIndex and self.colors.accentDim or self.colors.borderSoft)) else row:Hide() end end
        local selected=report.lockouts[self.selectedRaidIndex or 1]; page.raid.bossTitle:SetText(selected and (selected.name.."  •  "..selected.difficultyName) or self:T("NO_RAID_LOCKOUT"))
        for i,row in ipairs(page.raid.bossRows) do local boss=selected and selected.bosses[i]; if boss then row:Show(); row.icon:SetTexture(boss.icon or self.icons.raid); row.label:SetText(boss.name); row.value:SetText(self:T(boss.killed and "KILLED" or "AVAILABLE")); row.value:SetTextColor(unpack(boss.killed and self.colors.green or self.colors.yellow)) else row:Hide() end end
    else
        local d=self:GetDelvesReport(); page.delves.cards.season.value:SetText(d.season>0 and tostring(d.season) or self:T("NA")); page.delves.cards.tier.value:SetText(d.activeTier>0 and tostring(d.activeTier) or self:T("NA")); page.delves.cards.active.value:SetText(self:T(d.hasActive and "YES" or "NO")); page.delves.cards.reward.value:SetText(self:T(d.rewardEligible and "YES" or "NO"));
        local g=d.worldProgress; if g and g.threshold>0 then self:SetProgress(page.delves.weekly.bar,g.progress,g.threshold,""); page.delves.weekly.value:SetText(g.progress.." / "..g.threshold) else self:SetProgress(page.delves.weekly.bar,0,1,""); page.delves.weekly.value:SetText(self:T("NO_DATA")) end
        page.delves.locations.text:SetText(self:T("SEASON_DELVES")); page.delves.locations.note:SetText(self:T("DELVE_LIVE_NOTE"))
    end
end

function WN:CreateAltsSection(parent)
    local page=CreateFrame("Frame",nil,parent); page:SetAllPoints(); self.sections.alts=page; self:CreateSectionHeader(page)
    page.cards={}
    local defs={{"CHARACTER_COUNT","characters"},{"ALT_TOTAL_GOLD","gold"},{"VAULT_SLOTS","vault"},{"READY_TO_CLAIM","ready"}}
    for i,def in ipairs(defs) do
        local card=self:CreatePanel(page,self:T(def[1]),208,86)
        card:SetPoint("TOPLEFT",(i-1)*218,-48)
        card.value=self:CreateText(card,22,self.colors.text,"CENTER"); card.value:SetPoint("CENTER",0,-10)
        page.cards[def[2]]=card
    end

    page.list=self:CreatePanel(page,self:T("ACCOUNT_WEEKLY"),862,336); page.list:SetPoint("TOPLEFT",0,-144); page.list.rows={}; page.altPage=1
    page.list.title:ClearAllPoints(); page.list.title:SetPoint("TOPLEFT",12,-10); page.list.title:SetPoint("RIGHT",-330,0)
    page.list.empty=self:CreateText(page.list,11,self.colors.muted,"CENTER"); page.list.empty:SetPoint("TOPLEFT",14,-92); page.list.empty:SetPoint("RIGHT",-14,0); page.list.empty:SetHeight(76)

    page.list.sortButtons={}
    local sortDefs={{"ALT_SORT_VAULT","vault",58},{"ALT_SORT_LAST","lastSeen",82},{"ALT_SORT_MPLUS","mythicRating",52},{"ALT_SORT_ILVL","itemLevel",52}}
    local previous=nil
    for i,def in ipairs(sortDefs) do
        local mode=def[2]
        local b=self:CreateButton(page.list,def[3],22,self:T(def[1]),function() WN:SetAltSort(mode) end)
        if not previous then b:SetPoint("TOPRIGHT",-10,-5) else b:SetPoint("RIGHT",previous,"LEFT",-5,0) end
        b.sortKey=def[1]
        page.list.sortButtons[mode]=b
        previous=b
    end
    page.list.sortLabel=self:CreateText(page.list,8,self.colors.muted,"RIGHT"); page.list.sortLabel:SetPoint("RIGHT",previous,"LEFT",-8,0); page.list.sortLabel:SetText(self:T("ALT_SORT"))

    for i=1,5 do
        local row=ClickRow(page.list,840,48); row:SetPoint("TOPLEFT",10,-40-(i-1)*51); row.right:SetWidth(300); row.right:SetHeight(38); row.right:SetJustifyV("MIDDLE")
        local font=select(1,row.right:GetFont()); if font then row.right:SetFont(font,8,"") end
        row.icon:SetSize(34,34)
        row.name:ClearAllPoints(); row.name:SetPoint("LEFT",row.icon,"RIGHT",10,7); row.name:SetPoint("RIGHT",row.right,"LEFT",-12,7)
        row.meta:ClearAllPoints(); row.meta:SetPoint("LEFT",row.icon,"RIGHT",10,-10); row.meta:SetPoint("RIGHT",row.right,"LEFT",-12,-10)
        row.classBar=row:CreateTexture(nil,"ARTWORK"); row.classBar:SetPoint("TOPLEFT",1,-1); row.classBar:SetPoint("BOTTOMLEFT",1,1); row.classBar:SetWidth(3)
        row.faction=row:CreateTexture(nil,"OVERLAY"); row.faction:SetSize(17,17); row.faction:SetPoint("BOTTOMRIGHT",row.icon,"BOTTOMRIGHT",5,-4)
        row:RegisterForClicks("LeftButtonUp","RightButtonUp")
        row:SetScript("OnClick",function(r,button) if button=="RightButton" and r.characterKey then WN:DeleteCharacterSnapshot(r.characterKey) end end)
        page.list.rows[i]=row
    end
    page.list.prev=self:CreateButton(page.list,30,24,"‹",function() page.altPage=math.max(1,(page.altPage or 1)-1); WN:UpdateAltsSection() end); page.list.prev:SetPoint("BOTTOMRIGHT",-78,7)
    page.list.pageText=self:CreateText(page.list,8,self.colors.muted,"CENTER"); page.list.pageText:SetPoint("RIGHT",page.list.prev,"RIGHT",48,0); page.list.pageText:SetWidth(48)
    page.list.next=self:CreateButton(page.list,30,24,"›",function() page.altPage=(page.altPage or 1)+1; WN:UpdateAltsSection() end); page.list.next:SetPoint("BOTTOMRIGHT",-10,7)
end

function WN:UpdateAltsSection()
    local page=self.sections.alts; if not page then return end

    if self.cachedOverview and self.db and self.db.settings and self.db.settings.autoSnapshot ~= false then
        local now = type(GetTime)=="function" and GetTime() or 0
        local detailed = not self.lastAltDetailedSnapshot or (now - self.lastAltDetailedSnapshot) >= 60
        self:SaveCharacterSnapshot(self.cachedOverview, detailed)
        if detailed then self.lastAltDetailedSnapshot = now end
    end

    page.headerTitle:SetText(self:T("ALTS_TITLE")); page.headerSub:SetText(self:T("ALTS_SUB")); page.list.title:SetText(self:T("ACCOUNT_WEEKLY")); page.list.sortLabel:SetText(self:T("ALT_SORT"))
    local chars=self:GetCharacterSnapshots(); local account=self:GetAccountWeeklySummary()
    page.cards.characters.title:SetText(self:T("CHARACTER_COUNT")); page.cards.characters.value:SetText(tostring(account.characters or 0))
    page.cards.gold.title:SetText(self:T("ALT_TOTAL_GOLD")); page.cards.gold.value:SetText(self:FormatGoldCompact(account.money or 0).." g")
    page.cards.vault.title:SetText(self:T("VAULT_SLOTS")); page.cards.vault.value:SetText(tostring(account.completed or 0).."/"..tostring(account.total or 0))
    page.cards.ready.title:SetText(self:T("READY_TO_CLAIM")); page.cards.ready.value:SetText(tostring(account.ready or 0))

    local activeSort=(self.db and self.db.altSort) or "lastSeen"
    for mode,b in pairs(page.list.sortButtons) do
        b.label:SetText(self:T(b.sortKey))
        b:SetBackdropBorderColor(unpack(mode==activeSort and self.colors.accent or self.colors.borderSoft))
        b.label:SetTextColor(unpack(mode==activeSort and self.colors.accent or self.colors.text))
    end

    local perPage=5; local pages=math.max(1,math.ceil(#chars/perPage)); page.altPage=math.min(math.max(1,page.altPage or 1),pages); local startIndex=(page.altPage-1)*perPage
    page.list.pageText:SetText(page.altPage.."/"..pages); page.list.prev:SetEnabled(page.altPage>1); page.list.next:SetEnabled(page.altPage<pages)
    page.list.empty:SetText(self:T("NO_ALTS").."\n"..self:T("ALTS_SUB")); page.list.empty:SetShown(#chars==0)

    local currentKey=self:GetCharacterKey()
    for i,row in ipairs(page.list.rows) do
        local c=chars[startIndex+i]
        if c then
            row:Show(); row.characterKey=c.key
            row.icon:SetTexture(c.specIcon or self.icons.character)
            row.faction:SetTexture(self:GetFactionTexture(c.factionGroup))
            local cr,cg,cb=self:GetSnapshotClassColor(c)
            row.name:SetTextColor(cr,cg,cb,1); row.classBar:SetColorTexture(cr,cg,cb,1)
            row.name:SetText((c.name or "?").."  •  "..(c.realm or ""))
            row.meta:SetText(string.format("Lvl %s   •   %s   •   iLvl %.1f   •   M+ %s   •   %s %s%%", tostring(c.level or "?"), c.specName or "", tonumber(c.itemLevel) or 0, self:FormatNumber(c.mythicRating or 0), self:T("GEAR_SCORE_SHORT"), tostring(c.gearScore or 0)))

            local vaultText
            if c.staleWeekly then vaultText=self:T("VAULT_SLOTS").." ?" else vaultText=self:T("VAULT_SLOTS").." "..tostring(c.vaultDone or 0).."/"..tostring(c.vaultTotal or 0) end
            local mplusText=self:T("MPLUS_CENTER").." "..((tonumber(c.weeklyMPlusThreshold) or 0)>0 and (tostring(c.weeklyMPlusProgress or 0).."/"..tostring(c.weeklyMPlusThreshold)) or "?")
            local raidText
            if (tonumber(c.weeklyRaidThreshold) or 0)>0 then
                raidText=self:T("ALT_RAID").." "..tostring(c.weeklyRaidProgress or 0).."/"..tostring(c.weeklyRaidThreshold)
            elseif (tonumber(c.raidTotal) or 0)>0 then
                raidText=self:T("ALT_RAID").." "..tostring(c.raidKilled or 0).."/"..tostring(c.raidTotal)
            else
                raidText=self:T("ALT_RAID").." ?"
            end
            local delveText
            if (tonumber(c.weeklyDelveThreshold) or 0)>0 then
                delveText=self:T("ALT_DELVES").." "..tostring(c.weeklyDelveProgress or 0).."/"..tostring(c.weeklyDelveThreshold)
            elseif c.delveThreshold ~= nil and tonumber(c.delveThreshold) and tonumber(c.delveThreshold)>0 then
                delveText=self:T("ALT_DELVES").." "..tostring(c.delveProgress or 0).."/"..tostring(c.delveThreshold)
            elseif c.delveTier ~= nil and tonumber(c.delveTier) and tonumber(c.delveTier)>0 then
                delveText=self:T("ALT_DELVES").." T"..tostring(c.delveTier)
            else
                delveText=self:T("ALT_DELVES").." ?"
            end
            local seen=(c.lastSeen and date and date("%d.%m %H:%M",c.lastSeen)) or "-"
            local currentPrefix=(c.key==currentKey) and (self:T("ALT_CURRENT").."   •   ") or ""
            row.right:SetText(currentPrefix..vaultText.."   •   "..mplusText.."   •   "..raidText.."\n"..delveText.."   •   "..self:T("ALT_GOLD").." "..self:FormatGoldCompact(c.money or 0).." g   •   "..seen)

            local border=self.colors.borderSoft
            if c.key==currentKey then border=self.colors.accent elseif c.staleWeekly then border=self.colors.red elseif c.vaultAvailable then border=self.colors.green elseif (c.gearWarnings or 0)>0 then border=self.colors.yellow end
            row:SetBackdropBorderColor(unpack(border))
        else
            row:Hide(); row.characterKey=nil
        end
    end
end

-- Gear Doctor 3.1: sorted overview, enhancement advisor, BiS hover guidance and upgrade-source integration.
function WN:CreateGearSection(parent)
    local page=CreateFrame("Frame",nil,parent); page:SetAllPoints(); self.sections.gear=page; self:CreateSectionHeader(page); page.cards={}; page.mode="overview"
    local defs={"EQUIPPED_ILVL","GEAR_READINESS","WARNINGS","LOWEST_SLOT"}
    for i,key in ipairs(defs) do
        local card=self:CreatePanel(page,self:T(key),208,76); card:SetPoint("TOPLEFT",(i-1)*218,-48); card.value=self:CreateText(card,19,self.colors.text,"CENTER"); card.value:SetPoint("CENTER",0,-8); page.cards[key]=card
    end
    page.tabs={}
    local tabs={{"GEAR_OVERVIEW","overview"},{"GEAR_OPTIMIZE","optimize"},{"GEAR_UPGRADES","upgrades"}}
    for i,def in ipairs(tabs) do
        local b=self:CreateButton(page,282,28,self:T(def[1]),function() page.mode=def[2]; WN:UpdateGearSection(WN.cachedOverview or WN:GetOverviewData()) end)
        if i==1 then b:SetPoint("TOPLEFT",0,-132) else b:SetPoint("LEFT",page.tabs[tabs[i-1][2]],"RIGHT",8,0) end
        b.mode=def[2]; b.textKey=def[1]; page.tabs[def[2]]=b
    end

    -- Overview
    page.overview=CreateFrame("Frame",nil,page); page.overview:SetPoint("TOPLEFT",0,-170); page.overview:SetPoint("BOTTOMRIGHT",0,0)
    page.overview.actions=self:CreatePanel(page.overview,self:T("GEAR_ACTIONS"),326,310); page.overview.actions:SetPoint("TOPLEFT",0,0); page.overview.actions.rows={}
    page.overview.score=self:CreateText(page.overview.actions,28,self.colors.accent,"CENTER"); page.overview.score:SetPoint("TOP",0,-42)
    page.overview.bar=self:CreateProgressBar(page.overview.actions,290,12); page.overview.bar:SetPoint("TOP",0,-82)
    for i=1,5 do local row=self:CreateIconRow(page.overview.actions,300,38); row:SetPoint("TOPLEFT",12,-106-(i-1)*40); row.icon:SetSize(24,24); row.right:SetWidth(70); page.overview.actions.rows[i]=row end
    page.overview.items=self:CreatePanel(page.overview,self:T("GEAR"),526,310); page.overview.items:SetPoint("TOPLEFT",page.overview.actions,"TOPRIGHT",10,0); page.overview.items.rows={}
    for i=1,16 do local col=(i-1)%2; local rr=math.floor((i-1)/2); local row=self:CreateIconRow(page.overview.items,245,31); row.icon:SetSize(21,21); row:SetPoint("TOPLEFT",10+col*253,-38-rr*33); row.right:SetWidth(44); page.overview.items.rows[i]=row end

    -- Optimize
    page.optimize=CreateFrame("Frame",nil,page); page.optimize:SetPoint("TOPLEFT",0,-170); page.optimize:SetPoint("BOTTOMRIGHT",0,0)
    page.optimize.enchants=self:CreatePanel(page.optimize,self:T("CURRENT_RECOMMENDATIONS"),520,310); page.optimize.enchants:SetPoint("TOPLEFT",0,0); page.optimize.enchants.rows={}
    for i=1,7 do local row=self:CreateIconRow(page.optimize.enchants,494,34); row:SetPoint("TOPLEFT",12,-40-(i-1)*36); row.icon:SetSize(22,22); row.right:SetWidth(62); page.optimize.enchants.rows[i]=row end
    page.optimize.gems=self:CreatePanel(page.optimize,self:T("GEAR_OPTIMIZE"),332,310); page.optimize.gems:SetPoint("TOPLEFT",page.optimize.enchants,"TOPRIGHT",10,0)
    page.optimize.gems.spec=self:CreateText(page.optimize.gems,12,self.colors.text,"LEFT"); page.optimize.gems.spec:SetPoint("TOPLEFT",14,-42); page.optimize.gems.spec:SetPoint("RIGHT",-14,0)
    page.optimize.gems.stat=self:CreateText(page.optimize.gems,9,self.colors.accent,"LEFT"); page.optimize.gems.stat:SetPoint("TOPLEFT",14,-68); page.optimize.gems.stat:SetPoint("RIGHT",-14,0); page.optimize.gems.stat:SetHeight(36)
    page.optimize.gems.rows={}; for i=1,3 do local row=Line(page.optimize.gems,-114-(i-1)*42,self.icons.gear); row.label:SetWidth(88); row.value:SetWidth(200); page.optimize.gems.rows[i]=row end
    page.optimize.gems.source=self:CreateText(page.optimize.gems,8,self.colors.muted,"LEFT"); page.optimize.gems.source:SetPoint("BOTTOMLEFT",14,52); page.optimize.gems.source:SetPoint("RIGHT",-14,0); page.optimize.gems.source:SetHeight(38)
    page.optimize.gems.archon=self:CreateButton(page.optimize.gems,304,28,self:T("COPY_ARCHON_ENCHANTS"),function() local u=WN:GetArchonAdvisorURLs(); if u then WN:ShowCopyLink(WN:T("ARCHON_LIVE"),u.enchants) end end); page.optimize.gems.archon:SetPoint("BOTTOM",0,14)

    -- Upgrade sources
    page.upgrades=CreateFrame("Frame",nil,page); page.upgrades:SetPoint("TOPLEFT",0,-170); page.upgrades:SetPoint("BOTTOMRIGHT",0,0)
    page.upgrades.loot=self:CreatePanel(page.upgrades,self:T("KEYSTONELOOT_DATA"),560,310); page.upgrades.loot:SetPoint("TOPLEFT",0,0); page.upgrades.loot.rows={}
    for i=1,6 do local row=self:CreateIconRow(page.upgrades.loot,534,40); row:SetPoint("TOPLEFT",12,-42-(i-1)*42); row.icon:SetSize(26,26); row.right:SetWidth(112); page.upgrades.loot.rows[i]=row end
    page.upgrades.source=self:CreatePanel(page.upgrades,self:T("RECOMMENDATION_SOURCE"),292,310); page.upgrades.source:SetPoint("TOPLEFT",page.upgrades.loot,"TOPRIGHT",10,0)
    page.upgrades.source.status=self:CreateText(page.upgrades.source,11,self.colors.text,"LEFT"); page.upgrades.source.status:SetPoint("TOPLEFT",14,-44); page.upgrades.source.status:SetPoint("RIGHT",-14,0); page.upgrades.source.status:SetHeight(56)
    page.upgrades.source.note=self:CreateText(page.upgrades.source,8,self.colors.muted,"LEFT"); page.upgrades.source.note:SetPoint("TOPLEFT",14,-108); page.upgrades.source.note:SetPoint("RIGHT",-14,0); page.upgrades.source.note:SetHeight(82)
    page.upgrades.source.archon=self:CreateButton(page.upgrades.source,264,28,self:T("COPY_ARCHON_GEAR"),function() local u=WN:GetArchonAdvisorURLs(); if u then WN:ShowCopyLink(WN:T("ARCHON_LIVE"),u.gear) end end); page.upgrades.source.archon:SetPoint("BOTTOM",0,52)
    page.upgrades.source.adventure=self:CreateButton(page.upgrades.source,264,28,self:T("ADVENTURE_GUIDE"),function() WN:OpenTool("adventure") end); page.upgrades.source.adventure:SetPoint("BOTTOM",0,16)
end

function WN:UpdateGearSection(data)
    local page=self.sections.gear; if not page then return end
    data=data or self.cachedOverview or self:GetOverviewData(); local e=data.equipment
    page.headerTitle:SetText("Gear Doctor 3.1"); page.headerSub:SetText(self:T("SOURCE_ACCURACY"))
    local warn=(e.missingItems or 0)+(e.missingEnchants or 0)+(e.emptySockets or 0); local lowest=e.upgradeCandidates and e.upgradeCandidates[1]
    page.cards.EQUIPPED_ILVL.value:SetText(string.format("%.1f",data.character.equippedItemLevel or 0)); page.cards.GEAR_READINESS.value:SetText((e.score or 0).."%"); page.cards.WARNINGS.value:SetText(warn); page.cards.LOWEST_SLOT.value:SetText(lowest and (lowest.slotName.." "..math.floor((lowest.itemLevel or 0)+0.5)) or self:T("NA"))
    for mode,b in pairs(page.tabs) do b.label:SetText(self:T(b.textKey)); b:SetBackdropBorderColor(unpack(mode==page.mode and self.colors.accent or self.colors.borderSoft)) end
    page.overview:SetShown(page.mode=="overview"); page.optimize:SetShown(page.mode=="optimize"); page.upgrades:SetShown(page.mode=="upgrades")

    if page.mode=="overview" then
        page.overview.actions.title:SetText(self:T("GEAR_ACTIONS")); page.overview.items.title:SetText(self:T("GEAR")); page.overview.score:SetText((e.score or 0).."%"); page.overview.score:SetTextColor(unpack(e.complete and self.colors.green or self.colors.yellow)); self:SetProgress(page.overview.bar,e.score or 0,100,"")
        local actions=self:GetGearActionList(data)
        for i,row in ipairs(page.overview.actions.rows) do local a=actions[i]; if a then row:Show(); row.icon:SetTexture(a.icon or self.icons.warning); row.name:SetText(a.title or ""); row.meta:SetText(a.meta or ""); row.right:SetText(a.value or ""); row:SetBackdropBorderColor(unpack(a.priority>=80 and self.colors.yellow or self.colors.borderSoft)) else row:Hide() end end
        for i,row in ipairs(page.overview.items.rows) do
            local item=e.items[i]
            if item then
                row:Show(); row.icon:SetTexture(item.icon or self.icons.gear); row.name:SetText(item.slotName or ""); row.meta:SetText(item.name or ""); row.right:SetText(item.itemLevel and item.itemLevel>0 and tostring(math.floor(item.itemLevel+0.5)) or "")
                local issue=item.status=="missing" or item.missingEnchant or (item.emptySockets or 0)>0; row:SetBackdropBorderColor(unpack(issue and self.colors.yellow or self.colors.borderSoft)); row.itemLink=item.link; row.slotID=item.slotID
                row:SetScript("OnEnter",function(r)
                    r:SetBackdropColor(unpack(WN.colors.panelHover))
                    if r.itemLink then
                        local tip=WN:PrepareHoverTooltip(r,"ANCHOR_RIGHT")
                        tip:SetHyperlink(r.itemLink)
                        WN:AddBestInSlotToTooltip(tip,r.slotID,r.itemLink)
                        tip:Show()
                    end
                end); row:SetScript("OnLeave",function(r) r:SetBackdropColor(unpack(WN.colors.panel)); WN:HideHoverTooltip() end)
            else row:Hide(); row.itemLink=nil; row.slotID=nil end
        end
    elseif page.mode=="optimize" then
        local advisor=self:GetEnhancementAdvisor(); local snap=advisor.snapshot; page.optimize.enchants.title:SetText(self:T("CURRENT_RECOMMENDATIONS")); page.optimize.gems.title:SetText(self:T("GEAR_OPTIMIZE")); page.optimize.gems.spec:SetText((advisor.spec.specName or "").."  •  "..self:T("ARCHON_LIVE"))
        if snap then
            page.optimize.gems.stat:SetText(self:T("STAT_PRIORITY")..":\n"..snap.statPriority)
            for i,row in ipairs(page.optimize.enchants.rows) do local en=snap.enchants[i]; if en then row:Show(); row.icon:SetTexture(self.icons.check); row.name:SetText(en.slot); row.meta:SetText(en.name); row.right:SetText(en.popularity or "") else row:Hide() end end
            for i,row in ipairs(page.optimize.gems.rows) do local g=snap.gems[i]; if g then row:Show(); row.label:SetText(g.kind); row.value:SetText(g.name.."  "..(g.popularity or "")); row.icon:SetTexture(self.icons.gear) else row:Hide() end end
            page.optimize.gems.source:SetText(self:T("DATA_SNAPSHOT")..": "..snap.date.."\nArchon • "..snap.content.." • "..snap.sample)
        else
            page.optimize.gems.stat:SetText(self:T("NO_VERIFIED_SPEC_DATA")); for _,row in ipairs(page.optimize.enchants.rows) do row:Hide() end; for _,row in ipairs(page.optimize.gems.rows) do row:Hide() end; page.optimize.gems.source:SetText(self:T("SOURCE_ACCURACY"))
        end
        page.optimize.gems.archon:SetShown(advisor.urls~=nil)
    else
        local kl=self:GetKeystoneLootAdvisor(6); page.upgrades.loot.title:SetText(self:T("KEYSTONELOOT_DATA")); page.upgrades.source.title:SetText(self:T("RECOMMENDATION_SOURCE"))
        for i,row in ipairs(page.upgrades.loot.rows) do local it=kl.entries[i]; if it then row:Show(); row.icon:SetTexture(it.icon or self.icons.gear); row.name:SetText(it.name); row.meta:SetText(it.tierName or ""); row.right:SetText(it.source or ""); row:SetBackdropBorderColor(unpack(it.tier==3 and self.colors.green or it.tier==2 and self.colors.accentDim or self.colors.borderSoft)) else row:Hide() end end
        if kl.available then
            local activeText=(self.activeLocale=="deDE") and "aktiv" or "active"
            local countText=(self.activeLocale=="deDE") and (tostring(#kl.entries).." Empfehlungen für ") or (tostring(#kl.entries).." recommendations for ")
            page.upgrades.source.status:SetText("KeystoneLoot API: |cff55ff88"..activeText.."|r\n"..countText..(self:GetActiveSpecIdentity().specName or ""))
            page.upgrades.source.note:SetText(kl.message or self:T("BIS_META_NOTE"))
        else
            local missingText=(self.activeLocale=="deDE") and "nicht geladen" or "not loaded"
            page.upgrades.source.status:SetText("KeystoneLoot API: |cffffaa33"..missingText.."|r")
            page.upgrades.source.note:SetText(self:T("KEYSTONELOOT_MISSING"))
        end
        page.upgrades.source.archon:SetShown(self:GetArchonAdvisorURLs()~=nil)
    end
end

function WN:CreateToolsSection(parent)
    local page=CreateFrame("Frame",nil,parent); page:SetAllPoints(); self.sections.tools=page; self:CreateSectionHeader(page)
    page.quick=self:CreatePanel(page,self:T("COMMAND_CENTER"),862,90); page.quick:SetPoint("TOPLEFT",0,-48); local defs={{"GROUP_FINDER","group",self.icons.group},{"CALENDAR","calendar",self.icons.calendar},{"MAP","map",self.icons.map},{"CHARACTER","character",self.icons.character},{"PROFESSIONS","professions",self.icons.profession},{"COLLECTIONS","collections",self.icons.collections},{"ADVENTURE_GUIDE","adventure",self.icons.info},{"TALENTS","talents",self.icons.talents}}
    page.quick.buttons={}; for i,def in ipairs(defs) do local b=self:CreateIconButton(page.quick,96,50,def[3],self:T(def[1]),nil); b:SetPoint("TOPLEFT",10+(i-1)*105,-35); b.action=def[2]; b.textKey=def[1]; b:RegisterForClicks("LeftButtonUp","RightButtonUp"); b:SetScript("OnClick",function(btn,mouseButton) if mouseButton=="RightButton" then WN:ToggleFavorite(btn.action,WN:T(btn.textKey)) else WN:OpenTool(btn.action) end end); page.quick.buttons[i]=b end
    page.performance=self:CreatePanel(page,self:T("PERF_MANAGER"),270,332); page.performance:SetPoint("TOPLEFT",0,-148); page.performance.lines={}; for i,key in ipairs({"FPS","FRAME_TIME","WORLD_LATENCY","ADDON_MEMORY","ADDON_CPU","BAGS"}) do local row=Line(page.performance,-40-(i-1)*34,self.icons.info); row.label:SetText(self:T(key)); page.performance.lines[key]=row end; page.performance.top=self:CreateText(page.performance,8,self.colors.muted,"LEFT"); page.performance.top:SetPoint("TOPLEFT",12,-250); page.performance.top:SetPoint("RIGHT",-12,0); page.performance.top:SetHeight(56)
    page.addons=self:CreatePanel(page,self:T("COMMAND_CENTER"),370,332); page.addons:SetPoint("TOPLEFT",page.performance,"TOPRIGHT",10,0); page.addons.summary=self:CreateText(page.addons,8,self.colors.muted,"LEFT"); page.addons.summary:SetPoint("TOPLEFT",10,-38); page.addons.summary:SetPoint("RIGHT",-10,0); page.addons.scroll,page.addons.scrollChild=self:CreateScrollArea(page.addons,8,-56,-28,8); page.addons.rows={}; for i=1,30 do local row=self:CreateIconRow(page.addons.scrollChild,326,34); row:SetPoint("TOPLEFT",0,-(i-1)*37); row.icon:SetSize(22,22); row.right:SetWidth(70); row:RegisterForClicks("LeftButtonUp"); row:SetScript("OnClick",function(r) if r.addonData then WN:TryOpenAddonSettings(r.addonData) end end); page.addons.rows[i]=row end; page.addons.scrollChild:SetSize(326,30*37)
    page.integrations=self:CreatePanel(page,self:T("INTEGRATIONS"),202,332); page.integrations:SetPoint("TOPLEFT",page.addons,"TOPRIGHT",10,0); page.integrations.rows={}; for i=1,9 do local row=Line(page.integrations,-40-(i-1)*32,self.icons.info); row.label:SetWidth(105); row.value:SetWidth(68); page.integrations.rows[i]=row end; page.integrations.note=self:CreateText(page.integrations,8,self.colors.faint,"LEFT"); page.integrations.note:SetPoint("BOTTOMLEFT",10,10); page.integrations.note:SetPoint("RIGHT",-10,0); page.integrations.note:SetHeight(42)
end

function WN:UpdateToolsSection()
    local page=self.sections.tools; if not page then return end; page.headerTitle:SetText(self:T("TOOLS_TITLE")); page.headerSub:SetText(self:T("TOOLS_SUB")); page.quick.title:SetText(self:T("COMMAND_CENTER")); page.performance.title:SetText(self:T("PERF_MANAGER")); page.addons.title:SetText(self:T("COMMAND_CENTER")); page.integrations.title:SetText(self:T("INTEGRATIONS"))
    local report=self:GetAddonReport(); local p=self:UpdatePerformanceSnapshot(1,false,true); if report and report.memory then p.memory=report.memory end; local bags=self:GetBagReport(); local memory=p.memory and self:FormatMemory(p.memory) or self:T("NA"); local cpu=p.cpuEnabled and (p.cpuPercent and string.format("%.1f%%",p.cpuPercent) or self:T("NA")) or self:T("CPU_PROFILE_OFF"); local vals={FPS=string.format("%.0f",p.fps or 0),FRAME_TIME=string.format("%.1f ms",p.frameTime or 0),WORLD_LATENCY=(p.world or 0).." ms",ADDON_MEMORY=memory,ADDON_CPU=cpu,BAGS=(bags.free or 0).."/"..(bags.total or 0)}; for key,row in pairs(page.performance.lines) do row.label:SetText(self:T(key)); row.value:SetText(vals[key] or "") end
    local top={}; for _,a in ipairs(report.addons) do if a.loaded then top[#top+1]=a end end; table.sort(top,function(a,b) return (a.memory or 0)>(b.memory or 0) end); local t={self:T("TOP_MEMORY")..":"}; for i=1,math.min(3,#top) do t[#t+1]=(top[i].title or top[i].name).." "..self:FormatMemory(top[i].memory or 0) end; page.performance.top:SetText(table.concat(t,"\n"))
    local filter=string.lower(self.db.addonFilter or ""); local shown={}; for _,a in ipairs(report.addons) do if filter=="" or string.find(string.lower(a.title or ""),filter,1,true) or string.find(string.lower(a.name or ""),filter,1,true) then shown[#shown+1]=a end end; page.addons.summary:SetText(string.format("%s %d/%d   •   %s %s",self:T("LOADED"),report.loaded,report.total,self:T("MEMORY"),self:FormatMemory(report.memory))); page.addons.scrollChild:SetHeight(math.max(1,#shown)*37)
    for i=#page.addons.rows+1,#shown do
        local row=self:CreateIconRow(page.addons.scrollChild,326,34); row:SetPoint("TOPLEFT",0,-(i-1)*37); row.icon:SetSize(22,22); row.right:SetWidth(70); row:RegisterForClicks("LeftButtonUp"); row:SetScript("OnClick",function(r) if r.addonData then WN:TryOpenAddonSettings(r.addonData) end end); page.addons.rows[i]=row
    end
    for i,row in ipairs(page.addons.rows) do local a=shown[i]; if a then row:Show(); row.addonData=a; row.icon:SetTexture(a.loaded and self.icons.check or self.icons.info); row.name:SetText(a.title); row.meta:SetText((a.version~="" and ("v"..a.version.." • ") or "")..(a.category~="" and (a.category.." • ") or "")..(a.loaded and self:T("LOADED") or a.enabled and self:T("ENABLED") or self:T("DISABLED"))); row.right:SetText(self:FormatMemory(a.memory)); row:SetBackdropBorderColor(unpack(a.loaded and self.colors.borderSoft or self.colors.panel2)) else row:Hide(); row.addonData=nil end end
    local integrations=self:GetIntegrationReport(); for i,row in ipairs(page.integrations.rows) do local it=integrations[i]; if it then row:Show(); row.label:SetText(it.label); row.value:SetText(self:T(it.disabled and "DISABLED" or it.detected and "DETECTED" or "NOT_DETECTED")); row.value:SetTextColor(unpack(it.detected and self.colors.green or self.colors.muted)) else row:Hide() end end; page.integrations.note:SetText(self:T("INTEGRATION_NOTE"))
    for _,b in ipairs(page.quick.buttons) do local fav=self:IsFavorite(b.action); b:SetBackdropBorderColor(unpack(fav and self.colors.accent or self.colors.borderSoft)); b.label:SetText((fav and "★ " or "")..self:T(b.textKey)) end
end
