local _, WN = ...

local palettes = {
    midnight = {
        bg={0.025,0.045,0.055,0.97}, bgDeep={0.012,0.025,0.032,0.99}, panel={0.035,0.065,0.078,0.97}, panel2={0.045,0.082,0.095,0.97},
        panelHover={0.060,0.105,0.120,0.98}, border={0.115,0.190,0.215,1}, borderSoft={0.075,0.130,0.150,1}, accent={0.070,0.820,0.815,1}, accentDim={0.035,0.390,0.410,1}, accentBlue={0.10,0.58,0.92,1},
    },
    minimal = {
        bg={0.025,0.027,0.030,0.98}, bgDeep={0.012,0.013,0.015,0.99}, panel={0.045,0.047,0.052,0.98}, panel2={0.060,0.062,0.068,0.98},
        panelHover={0.085,0.087,0.095,0.99}, border={0.18,0.19,0.21,1}, borderSoft={0.11,0.12,0.14,1}, accent={0.72,0.76,0.82,1}, accentDim={0.35,0.38,0.43,1}, accentBlue={0.38,0.63,0.93,1},
    },
    transparent = {
        bg={0.015,0.025,0.032,0.82}, bgDeep={0.010,0.018,0.024,0.88}, panel={0.025,0.050,0.060,0.80}, panel2={0.035,0.068,0.080,0.84},
        panelHover={0.050,0.095,0.108,0.90}, border={0.10,0.30,0.34,0.90}, borderSoft={0.07,0.20,0.23,0.82}, accent={0.07,0.82,0.815,1}, accentDim={0.035,0.39,0.41,1}, accentBlue={0.10,0.58,0.92,1},
    },
    ulathek = {
        bg={0.028,0.033,0.018,0.98}, bgDeep={0.014,0.018,0.009,0.99}, panel={0.036,0.044,0.020,0.97}, panel2={0.050,0.060,0.028,0.97},
        panelHover={0.070,0.090,0.040,0.98}, border={0.31,0.58,0.10,1}, borderSoft={0.18,0.34,0.08,1}, accent={0.69,0.97,0.22,1}, accentDim={0.20,0.36,0.08,1}, accentBlue={0.82,0.98,0.42,1},
    },
    custom = {
        bg={0.025,0.045,0.055,0.97}, bgDeep={0.012,0.025,0.032,0.99}, panel={0.035,0.065,0.078,0.97}, panel2={0.045,0.082,0.095,0.97},
        panelHover={0.060,0.105,0.120,0.98}, border={0.115,0.190,0.215,1}, borderSoft={0.075,0.130,0.150,1}, accent={0.070,0.820,0.815,1}, accentDim={0.035,0.390,0.410,1}, accentBlue={0.10,0.58,0.92,1},
    },
}

local themeOrder = { "ulathek", "midnight", "minimal", "transparent", "class", "custom" }
WN.themeOrder = themeOrder
WN.themeNames = { ulathek="THEME_ULATEK", midnight="THEME_MIDNIGHT", minimal="THEME_MINIMAL", transparent="THEME_TRANSPARENT", class="THEME_CLASS", custom="THEME_CUSTOM" }

local function CopyColor(target, source)
    if type(target) ~= "table" or type(source) ~= "table" then return end
    for i=1,4 do target[i] = source[i] or target[i] or 1 end
end

function WN:ApplyTheme(theme, refreshFrames)
    theme = theme or "midnight"
    local palette = palettes[theme] or palettes.midnight
    for key, color in pairs(palette) do
        if self.colors[key] then CopyColor(self.colors[key], color) end
    end
    if theme == "class" then
        local _, classFile = UnitClass and UnitClass("player")
        local c = classFile and RAID_CLASS_COLORS and RAID_CLASS_COLORS[classFile]
        if c then
            CopyColor(self.colors.accent, {c.r,c.g,c.b,1})
            CopyColor(self.colors.accentDim, {c.r*0.48,c.g*0.48,c.b*0.48,1})
            CopyColor(self.colors.border, {c.r*0.45,c.g*0.45,c.b*0.45,1})
        end
    end
    if theme == "custom" then
        local c = self.db and self.db.settings and self.db.settings.customAccent or {0.07,0.82,0.815}
        local r = tonumber(c[1]) or 0.07
        local g = tonumber(c[2]) or 0.82
        local b = tonumber(c[3]) or 0.815
        CopyColor(self.colors.accent, {r,g,b,1})
        CopyColor(self.colors.accentDim, {r*0.48,g*0.48,b*0.48,1})
        CopyColor(self.colors.border, {math.max(r*0.42,0.08),math.max(g*0.42,0.08),math.max(b*0.42,0.08),1})
    end
    if self.db and self.db.settings then self.db.settings.theme = theme end
    if refreshFrames and self.RefreshThemeFrames then self:RefreshThemeFrames() end
    if self.RefreshEditorGrid then self:RefreshEditorGrid() end
end

function WN:CycleTheme()
    local current = self.db.settings.theme or "midnight"
    local idx = 1
    for i,v in ipairs(themeOrder) do if v == current then idx = i break end end
    idx = (idx % #themeOrder) + 1
    local nextTheme = themeOrder[idx]
    self:ApplyTheme(nextTheme, true)
    self:UpdateSettingsSection()
    if nextTheme == "custom" then self:OpenCustomColorPicker() end
end

function WN:OpenCustomColorPicker()
    if not ColorPickerFrame or type(ColorPickerFrame.SetupColorPickerAndShow) ~= "function" or not self.db then return end
    local c = self.db.settings.customAccent or {0.07,0.82,0.815}
    local previous = { tonumber(c[1]) or 0.07, tonumber(c[2]) or 0.82, tonumber(c[3]) or 0.815 }
    local function ApplySelected()
        local r,g,b = ColorPickerFrame:GetColorRGB()
        WN.db.settings.customAccent = {r,g,b}
        WN:ApplyTheme("custom", true)
        WN:UpdateSettingsSection()
    end
    local function CancelSelected()
        WN.db.settings.customAccent = {previous[1],previous[2],previous[3]}
        WN:ApplyTheme("custom", true)
        WN:UpdateSettingsSection()
    end
    ColorPickerFrame:SetupColorPickerAndShow({
        swatchFunc = ApplySelected, cancelFunc = CancelSelected, hasOpacity = false,
        r = previous[1], g = previous[2], b = previous[3],
    })
end

function WN:GetThemeName()
    return self:T(self.themeNames[self.db and self.db.settings.theme or "midnight"] or "THEME_MIDNIGHT")
end

local searchMap = {
    overview={"overview","übersicht","home","dashboard"}, planner={"planner","planer","weekly","woche","vault","schatzkammer","task","aufgabe"},
    character={"character","charakter","stats","werte"}, gear={"gear","ausrüstung","gear doctor","enchant","verzauberung","socket","sockel"},
    activities={"activities","aktivitäten","season","saison"}, endgame={"endgame","mythic","mythisch","raid","schlachtzug","delve","tiefe","tiefen"},
    alts={"alts","twinks","charaktere","account"}, collections={"collections","sammlung","mount","reittier","pet","haustier","toy","spielzeug","transmog","erfolg"},
    tools={"tools","werkzeuge","addons","performance","leistung","integration"}, settings={"settings","einstellungen","theme","profil","profile","diagnose","safe"},
}

function WN:SearchNavigate(query)
    query = type(query)=="string" and strtrim(string.lower(query)) or ""
    if query == "" then return false end
    for section, terms in pairs(searchMap) do
        for _, term in ipairs(terms) do
            if string.find(query, term, 1, true) or string.find(term, query, 1, true) then
                self.db.addonFilter = nil
                self.frame:Show(); self:ShowSection(section)
                return true
            end
        end
    end
    local report = self:GetAddonReport()
    for _, addon in ipairs(report.addons) do
        local title = string.lower(addon.title or addon.name or "")
        local name = string.lower(addon.name or "")
        if string.find(title, query, 1, true) or string.find(name, query, 1, true) then
            self.db.addonFilter = query
            self.frame:Show(); self:ShowSection("tools")
            return true
        end
    end
    self:Print(self:T("SEARCH_NOT_FOUND", query))
    return false
end

function WN:CreateTaskDialog()
    if self.taskDialog then return self.taskDialog end
    local f = CreateFrame("Frame", "WoWNextTaskDialog", UIParent, "BackdropTemplate")
    f:SetSize(420,150); f:SetPoint("CENTER"); f:SetFrameStrata("DIALOG"); self.ApplyBackdrop(f,self.colors.bgDeep,self.colors.accentDim); f:Hide()
    f.title=self:CreateText(f,15,self.colors.text,"LEFT"); f.title:SetPoint("TOPLEFT",16,-16); f.title:SetText(self:T("ADD_TASK"))
    f.edit=CreateFrame("EditBox",nil,f,"InputBoxTemplate"); f.edit:SetPoint("TOPLEFT",18,-50); f.edit:SetPoint("TOPRIGHT",-18,-50); f.edit:SetHeight(30); f.edit:SetAutoFocus(false); f.edit:SetMaxLetters(120)
    f.add=self:CreateButton(f,120,32,self:T("ADD_TASK"),function() WN:AddCustomTask(f.edit:GetText()); f.edit:SetText(""); f:Hide() end); f.add:SetPoint("BOTTOMRIGHT",-16,14)
    f.cancel=self:CreateButton(f,100,32,CANCEL or "Cancel",function() f:Hide() end); f.cancel:SetPoint("RIGHT",f.add,"LEFT",-8,0)
    f.edit:SetScript("OnEnterPressed",function(self) WN:AddCustomTask(self:GetText()); self:SetText(""); f:Hide() end)
    f.edit:SetScript("OnEscapePressed",function() f:Hide() end)
    self.taskDialog=f; return f
end

function WN:OpenTaskDialog(text)
    local f=self:CreateTaskDialog(); f.title:SetText(self:T("ADD_TASK")); f.add.label:SetText(self:T("ADD_TASK")); f.edit:SetText(text or ""); f:Show(); f.edit:SetFocus()
end

function WN:CreateProfileDialog()
    if self.profileDialog then return self.profileDialog end
    local f=CreateFrame("Frame","WoWNextProfileDialog",UIParent,"BackdropTemplate")
    f:SetSize(620,210); f:SetPoint("CENTER"); f:SetFrameStrata("DIALOG"); self.ApplyBackdrop(f,self.colors.bgDeep,self.colors.accentDim); f:Hide()
    f.title=self:CreateText(f,15,self.colors.text,"LEFT"); f.title:SetPoint("TOPLEFT",16,-16)
    f.help=self:CreateText(f,9,self.colors.muted,"LEFT"); f.help:SetPoint("TOPLEFT",16,-42); f.help:SetPoint("RIGHT",-16,0)
    f.edit=CreateFrame("EditBox",nil,f,"InputBoxTemplate"); f.edit:SetPoint("TOPLEFT",18,-72); f.edit:SetPoint("TOPRIGHT",-18,-72); f.edit:SetHeight(34); f.edit:SetAutoFocus(false); f.edit:SetMaxLetters(4096)
    f.edit:SetScript("OnEscapePressed",function() f:Hide() end)
    f.action=self:CreateButton(f,120,32,"",function()
        if f.mode=="import" then
            if WN:ImportProfileString(f.edit:GetText()) then WN:Print(WN:T("PROFILE_IMPORTED")); f:Hide() else WN:Print(WN:T("PROFILE_INVALID")) end
        else
            f.edit:HighlightText(); f.edit:SetFocus()
        end
    end); f.action:SetPoint("BOTTOMRIGHT",-16,16)
    f.close=self:CreateButton(f,100,32,CLOSE or "Close",function() f:Hide() end); f.close:SetPoint("RIGHT",f.action,"LEFT",-8,0)
    self.profileDialog=f; return f
end

function WN:OpenProfileDialog(mode)
    local f=self:CreateProfileDialog(); f.mode=mode
    if mode=="import" then
        f.title:SetText(self:T("IMPORT_PROFILE")); f.help:SetText(self:T("PROFILE_IMPORT_HELP")); f.edit:SetText(""); f.action.label:SetText(self:T("IMPORT_PROFILE")); f:Show(); f.edit:SetFocus()
    else
        f.title:SetText(self:T("EXPORT_PROFILE")); f.help:SetText(self:T("PROFILE_EXPORT_HELP")); f.edit:SetText(self:ExportProfileString()); f.action.label:SetText(self:T("EXPORT_PROFILE")); f:Show(); f.edit:SetFocus(); f.edit:HighlightText()
    end
end

local widgetDefs = {
    performance={key="widgetPerformance", titleKey="WIDGET_PERFORMANCE", width=235}, character={key="widgetCharacter", titleKey="WIDGET_CHARACTER", width=235},
    currency={key="widgetCurrency", titleKey="WIDGET_CURRENCY", width=180}, weekly={key="widgetWeekly", titleKey="WIDGET_WEEKLY", width=220}, bags={key="widgetBags", titleKey="WIDGET_BAGS", width=180},
}

function WN:SaveWidgetPosition(id)
    if not self.widgets or not self.widgets[id] or not self.db then return end
    local widget = self.widgets[id]
    if self.editorMode and self.SnapFramePosition then self:SnapFramePosition(widget, 10) end
    local point,_,relativePoint,x,y=widget:GetPoint(1)
    self.db.positions["widget_"..id]={point=point,relativePoint=relativePoint,x=x,y=y}
end

function WN:RestoreWidgetPosition(id, fallbackX, fallbackY)
    local w=self.widgets and self.widgets[id]; if not w then return end
    local pos=self.db.positions and self.db.positions["widget_"..id]
    w:ClearAllPoints()
    if type(pos)=="table" and pos.point then w:SetPoint(pos.point,UIParent,pos.relativePoint or pos.point,pos.x or 0,pos.y or 0)
    else w:SetPoint("CENTER",UIParent,"CENTER",fallbackX or 0,fallbackY or 0) end
end

function WN:AnyHUDWidgetEnabled()
    if not self.db or not self.db.settings or self.db.settings.safeMode == true then return false end
    local s = self.db.settings
    return s.widgetPerformance == true or s.widgetCharacter == true or s.widgetCurrency == true or s.widgetWeekly == true or s.widgetBags == true
end

function WN:UpdateHUDTicker()
    local shouldRun = self:AnyHUDWidgetEnabled()
    if not shouldRun then
        if self.widgetTicker and type(self.widgetTicker.Cancel) == "function" then self.widgetTicker:Cancel() end
        self.widgetTicker = nil
        return
    end
    if self.widgetTicker then return end
    self.widgetTicker = C_Timer.NewTicker(2, function()
        if WN.widgets and WN:AnyHUDWidgetEnabled() then WN:UpdateHUDWidgets() end
    end)
end

function WN:CreateHUDWidgets()
    if self.widgets then return end
    self.widgets={}
    local order={"performance","character","currency","weekly","bags"}
    for i,id in ipairs(order) do
        local def=widgetDefs[id]
        local w=CreateFrame("Frame","WoWNextHUD"..id,UIParent,"BackdropTemplate"); w:SetSize(def.width,44); w:SetFrameStrata("MEDIUM"); w:SetClampedToScreen(true); w:SetMovable(true); w:EnableMouse(true)
        self.ApplyBackdrop(w,self.colors.bgDeep,self.colors.borderSoft)
        w.title=self:CreateText(w,8,self.colors.muted,"LEFT"); w.title:SetPoint("TOPLEFT",8,-6); w.title:SetText(self:T(def.titleKey))
        w.value=self:CreateText(w,11,self.colors.text,"LEFT"); w.value:SetPoint("BOTTOMLEFT",8,7); w.value:SetPoint("RIGHT",-8,0)
        w.id=id; w.settingKey=def.key
        w:RegisterForDrag("LeftButton")
        w:SetScript("OnDragStart",function(self) if WN.editorMode then self:StartMoving() end end)
        w:SetScript("OnDragStop",function(self) self:StopMovingOrSizing(); WN:SaveWidgetPosition(self.id) end)
        self.widgets[id]=w
        self:RestoreWidgetPosition(id,-390+(i-1)*195,300)
    end
    self:ApplySettings()
end

function WN:UpdateHUDWidgets()
    if not self.widgets or not self.db or not self.db.settings or self.db.settings.safeMode == true then return end
    local data=self.cachedOverview
    if not data then return end
    local s=self.db.settings
    local showPerformance=s.widgetPerformance == true
    local showCharacter=s.widgetCharacter == true
    local showCurrency=s.widgetCurrency == true
    local showWeekly=s.widgetWeekly == true
    local showBags=s.widgetBags == true
    if not (showPerformance or showCharacter or showCurrency or showWeekly or showBags) then return end

    if showPerformance and self.widgets.performance then
        local p=self:UpdatePerformanceSnapshot(2, false, false)
        self.widgets.performance.value:SetText(string.format("%.0f FPS   %.1f ms   %d ms",p.fps or 0,p.frameTime or 0,p.world or 0))
    end
    if showCharacter and self.widgets.character then
        self.widgets.character.value:SetText(string.format("iLvl %.1f   M+ %s   Gear %d%%",data.character.equippedItemLevel or 0,self:FormatNumber(data.character.mythicRating),data.equipment.score or 0))
    end
    if showCurrency and self.widgets.currency then self.widgets.currency.value:SetText(self:FormatMoney(data.currencies.money or 0)) end
    if showWeekly and self.widgets.weekly then
        local account=self:GetAccountWeeklySummary()
        self.widgets.weekly.value:SetText(string.format("%d/%d   %s %d",data.weekly.completedThresholds or 0,data.weekly.totalThresholds or 0,self:T("READY_TO_CLAIM"),account.ready or 0))
    end
    if showBags and self.widgets.bags then
        local bags=self:GetBagReport()
        self.widgets.bags.value:SetText(string.format("%s %d   %s %d",self:T("FREE_SLOTS"),bags.free or 0,self:T("USED_SLOTS"),bags.used or 0))
    end
end

function WN:ResetHUDPositions()
    if not self.db then return end
    for id in pairs(widgetDefs) do self.db.positions["widget_"..id]=nil end
    local i=0
    for _,id in ipairs({"performance","character","currency","weekly","bags"}) do i=i+1; self:RestoreWidgetPosition(id,-390+(i-1)*195,300) end
end

function WN:SnapFramePosition(frame, step)
    if not frame or type(frame.GetPoint) ~= "function" then return end
    step = tonumber(step) or 10
    if step < 1 then step = 1 end
    local point, relativeTo, relativePoint, x, y = frame:GetPoint(1)
    if not point then return end
    x = tonumber(x) or 0
    y = tonumber(y) or 0
    local function Snap(value)
        if value >= 0 then return math.floor((value / step) + 0.5) * step end
        return math.ceil((value / step) - 0.5) * step
    end
    frame:ClearAllPoints()
    frame:SetPoint(point, relativeTo or UIParent, relativePoint or point, Snap(x), Snap(y))
end

function WN:CreateEditorGrid()
    if self.editorGrid then return self.editorGrid end
    local grid = CreateFrame("Frame", "WoWNextEditorGrid", UIParent)
    grid:SetAllPoints(UIParent)
    grid:SetFrameStrata("BACKGROUND")
    grid:Hide()
    local step = 40
    local extent = 24
    grid.lines = {}
    for i = -extent, extent do
        local v = grid:CreateTexture(nil, "BACKGROUND")
        v:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], i == 0 and 0.22 or 0.07)
        v:SetWidth(i == 0 and 2 or 1)
        v:SetPoint("TOP", UIParent, "TOP", i * step, 0)
        v:SetPoint("BOTTOM", UIParent, "BOTTOM", i * step, 0)
        grid.lines[#grid.lines + 1] = v

        local h = grid:CreateTexture(nil, "BACKGROUND")
        h:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], i == 0 and 0.22 or 0.07)
        h:SetHeight(i == 0 and 2 or 1)
        h:SetPoint("LEFT", UIParent, "LEFT", 0, i * step)
        h:SetPoint("RIGHT", UIParent, "RIGHT", 0, i * step)
        grid.lines[#grid.lines + 1] = h
    end
    self.editorGrid = grid
    return grid
end

function WN:RefreshEditorGrid()
    if not self.editorGrid or not self.editorGrid.lines then return end
    for _, line in ipairs(self.editorGrid.lines) do
        line:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.08)
    end
end

function WN:ToggleEditorMode()
    self.editorMode=not self.editorMode
    local grid = self:CreateEditorGrid()
    grid:SetShown(self.editorMode)
    if self.frame then self.frame:SetBackdropBorderColor(unpack(self.editorMode and self.colors.accent or self.colors.border)) end
    if self.tracker then self.tracker:SetBackdropBorderColor(unpack(self.editorMode and self.colors.accent or self.colors.border)) end
    if self.widgets then for _,w in pairs(self.widgets) do w:SetBackdropBorderColor(unpack(self.editorMode and self.colors.accent or self.colors.borderSoft)) end end
    self:Print(self:T(self.editorMode and "EDIT_MODE_ON" or "EDIT_MODE_OFF"))
    self:UpdateSettingsSection()
end

function WN:CreateDiagnosticDialog()
    if self.diagnosticDialog then return self.diagnosticDialog end
    local f=CreateFrame("Frame","WoWNextDiagnosticDialog",UIParent,"BackdropTemplate")
    f:SetSize(700,430); f:SetPoint("CENTER"); f:SetFrameStrata("DIALOG"); self.ApplyBackdrop(f,self.colors.bgDeep,self.colors.accentDim); f:Hide()
    f.title=self:CreateText(f,15,self.colors.text,"LEFT"); f.title:SetPoint("TOPLEFT",16,-16); f.title:SetText(self:T("DIAGNOSTICS"))
    f.close=self:CreateButton(f,90,28,CLOSE or "Close",function() f:Hide() end); f.close:SetPoint("TOPRIGHT",-14,-12)
    f.scroll,f.child=self:CreateScrollArea(f,14,-50,-30,48); f.child:SetSize(640,1)
    f.text=self:CreateText(f.child,9,self.colors.text,"LEFT"); f.text:SetPoint("TOPLEFT",0,0); f.text:SetPoint("RIGHT",0,0); f.text:SetJustifyH("LEFT"); f.text:SetJustifyV("TOP")
    f.clear=self:CreateButton(f,120,28,self:T("CLEAR_LOG"),function() WN:ClearDiagnostics(); WN:OpenDiagnosticDialog() end); f.clear:SetPoint("BOTTOMRIGHT",-14,12)
    self.diagnosticDialog=f; return f
end

function WN:OpenDiagnosticDialog()
    self:InitDiagnostics()
    local f=self:CreateDiagnosticDialog()
    f.title:SetText(self:T("DIAGNOSTICS")); f.clear.label:SetText(self:T("CLEAR_LOG"))
    local lines={}
    lines[#lines+1]=self:T("ERRORS")..": "..#self.diagnostics.errors
    for i=#self.diagnostics.errors,1,-1 do
        local e=self.diagnostics.errors[i]
        lines[#lines+1]=string.format("[%s] %s: %s",tostring(e.time or ""),tostring(e.module or "?"),tostring(e.text or "?"))
    end
    lines[#lines+1]=""
    lines[#lines+1]=self:T("BLOCKED_ACTIONS")..": "..#self.diagnostics.blocked
    for i=#self.diagnostics.blocked,1,-1 do
        local e=self.diagnostics.blocked[i]
        lines[#lines+1]=string.format("[%s] %s: %s",tostring(e.time or ""),tostring(e.addon or "?"),tostring(e.func or "?"))
    end
    if #self.diagnostics.errors==0 and #self.diagnostics.blocked==0 then lines[#lines+1]=self:T("NO_DATA") end
    f.text:SetText(table.concat(lines,"\n"))
    local h=math.max(320,(#lines*18)+30); f.text:SetHeight(h); f.child:SetHeight(h)
    f:Show()
    return f
end

function WN:CreateMinimapDashboard()
    if self.minimapDashboard then return self.minimapDashboard end
    local f=CreateFrame("Frame","WoWNextMinimapDashboard",UIParent,"BackdropTemplate"); f:SetSize(220,292); f:SetFrameStrata("DIALOG"); self.ApplyBackdrop(f,self.colors.bgDeep,self.colors.accentDim); f:Hide()
    f.title=self:CreateText(f,13,self.colors.text,"LEFT"); f.title:SetPoint("TOPLEFT",12,-12); f.title:SetText(self:T("MINIMAP_DASHBOARD"))
    f.summary=self:CreateText(f,9,self.colors.muted,"LEFT"); f.summary:SetPoint("TOPLEFT",12,-36); f.summary:SetPoint("RIGHT",-12,0); f.summary:SetHeight(42)
    local defs={{"OVERVIEW","overview"},{"PLANNER","planner"},{"GEAR","gear"},{"ENDGAME","endgame"},{"ALTS","alts"},{"SETTINGS","settings"}}
    f.buttons={}
    for i,def in ipairs(defs) do
        local b=self:CreateButton(f,196,28,self:T(def[1]),function() WN.frame:Show(); WN:ShowSection(def[2]); f:Hide() end); b:SetPoint("TOPLEFT",12,-82-(i-1)*32); b.textKey=def[1]; f.buttons[i]=b
    end
    self.minimapDashboard=f; return f
end

function WN:ToggleMinimapDashboard()
    local f=self:CreateMinimapDashboard()
    if f:IsShown() then f:Hide(); return end
    f:ClearAllPoints(); f:SetPoint("TOPRIGHT",self.minimapButton,"BOTTOMLEFT",-4,-4)
    local data=self.cachedOverview or self:GetOverviewData(); f.summary:SetText(string.format("iLvl %.1f   M+ %s\n%s %d/%d",data.character.equippedItemLevel or 0,self:FormatNumber(data.character.mythicRating),self:T("GREAT_VAULT"),data.weekly.completedThresholds or 0,data.weekly.totalThresholds or 0)); f:Show()
end

function WN:TryOpenAddonSettings(addon)
    if InCombatLockdown and InCombatLockdown() then self:Print(self:T("COMBAT_BLOCKED")); return end
    local title=type(addon)=="table" and (addon.title or addon.name) or tostring(addon or "")
    if Settings and type(Settings.OpenToCategory)=="function" then
        local ok=pcall(Settings.OpenToCategory,title)
        if ok then return end
    end
    if type(InterfaceOptionsFrame_OpenToCategory)=="function" then pcall(InterfaceOptionsFrame_OpenToCategory,title) end
end

function WN:ExecutePlanAction(action)
    if not action then return end
    local sections = { overview=true, planner=true, character=true, gear=true, activities=true, endgame=true, alts=true, collections=true, tools=true, settings=true }
    if sections[action] then
        if self.frame then self.frame:Show(); self:ShowSection(action) end
    else
        self:OpenTool(action)
    end
end
