local _, WN = ...

local function ApplyBackdrop(frame, bg, border)
    frame:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        edgeSize = 1,
    })
    frame._wnBgColor = bg or WN.colors.panel
    frame._wnBorderColor = border or WN.colors.border
    frame:SetBackdropColor(unpack(frame._wnBgColor))
    frame:SetBackdropBorderColor(unpack(frame._wnBorderColor))
    WN.themedFrames = WN.themedFrames or {}
    WN.themedFrames[frame] = true
end
WN.ApplyBackdrop = ApplyBackdrop

function WN:RefreshThemeFrames()
    for frame in pairs(self.themedFrames or {}) do
        if frame and frame.SetBackdropColor and frame._wnBgColor and frame._wnBorderColor then
            frame:SetBackdropColor(unpack(frame._wnBgColor))
            frame:SetBackdropBorderColor(unpack(frame._wnBorderColor))
        end
    end
    if self.frame and self.frame.top then
        if self.frame.top.accentLine then self.frame.top.accentLine:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.82) end
        if self.frame.top.glow then self.frame.top.glow:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.12) end
        if self.frame.top.title then self.frame.top.title:SetTextColor(unpack(self.colors.text)) end
        if self.frame.top.season then self.frame.top.season:SetTextColor(unpack(self.colors.accent)) end
    end
    if self.frame and self.frame.pageTint then self.frame.pageTint:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.015) end
    if self.frame and self.frame.profile and self.frame.profile.tint then self.frame.profile.tint:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.018) end
    if self.frame and self.frame.nav and self.frame.nav.tint then self.frame.nav.tint:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.018) end
    if self.tracker then
        if self.tracker.tint then self.tracker.tint:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.018) end
        if self.tracker.top then
            if self.tracker.top.glow then self.tracker.top.glow:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.12) end
            if self.tracker.top.accentLine then self.tracker.top.accentLine:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.75) end
        end
    end
    if self.frame and self.frame.nav then
        if self.frame.nav.footer then self.frame.nav.footer:SetText(self.seasonName or "Midnight • Curse of Ula-Tek") end
    end
    if self.frame and self.frame.nav and self.frame.nav.buttons then
        self:ShowSection(self.activeSection or (self.db and self.db.activeSection) or "overview", true)
    end
end

function WN:CreateText(parent, size, color, justify, fontObject)
    local text = parent:CreateFontString(nil, "OVERLAY", fontObject or "GameFontNormal")
    local font = select(1, text:GetFont())
    text:SetFont(font, size or 12, "")
    text:SetTextColor(unpack(color or self.colors.text))
    text:SetJustifyH(justify or "LEFT")
    text:SetJustifyV("MIDDLE")
    return text
end

function WN:CreatePanel(parent, title, width, height)
    local panel = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    panel:SetSize(width, height)
    ApplyBackdrop(panel, self.colors.panel, self.colors.borderSoft)
    panel.title = self:CreateText(panel, 12, self.colors.text, "LEFT")
    panel.title:SetPoint("TOPLEFT", 13, -10)
    panel.title:SetPoint("RIGHT", -12, 0)
    panel.title:SetText(title or "")
    panel.rule = panel:CreateTexture(nil, "ARTWORK")
    panel.rule:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.35)
    panel.rule:SetPoint("TOPLEFT", 12, -32)
    panel.rule:SetPoint("TOPRIGHT", -12, -32)
    panel.rule:SetHeight(1)
    return panel
end

function WN:CreateButton(parent, width, height, text, onClick)
    local button = CreateFrame("Button", nil, parent, "BackdropTemplate")
    button:SetSize(width, height)
    ApplyBackdrop(button, self.colors.panel2, self.colors.borderSoft)
    button.label = self:CreateText(button, 11, self.colors.text, "CENTER")
    button.label:SetPoint("LEFT", 6, 0)
    button.label:SetPoint("RIGHT", -6, 0)
    button.label:SetText(text or "")
    button:SetScript("OnEnter", function(self)
        self:SetBackdropColor(unpack(WN.colors.panelHover))
        self:SetBackdropBorderColor(unpack(WN.colors.accentDim))
    end)
    button:SetScript("OnLeave", function(self)
        self:SetBackdropColor(unpack(WN.colors.panel2))
        self:SetBackdropBorderColor(unpack(WN.colors.borderSoft))
    end)
    if onClick then button:SetScript("OnClick", onClick) end
    return button
end

function WN:CreateIconButton(parent, width, height, icon, text, onClick)
    local button = self:CreateButton(parent, width, height, "", onClick)
    button.icon = button:CreateTexture(nil, "ARTWORK")
    button.icon:SetSize(18, 18)
    button.icon:SetPoint("TOP", 0, -8)
    button.icon:SetTexture(icon or self.icons.info)
    button.label:ClearAllPoints()
    button.label:SetPoint("BOTTOMLEFT", 4, 6)
    button.label:SetPoint("BOTTOMRIGHT", -4, 6)
    button.label:SetText(text or "")
    return button
end

function WN:CreateProgressBar(parent, width, height)
    local bar = CreateFrame("StatusBar", nil, parent, "BackdropTemplate")
    bar:SetSize(width, height)
    bar:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8")
    bar:SetStatusBarColor(unpack(self.colors.accent))
    bar:SetMinMaxValues(0, 1)
    bar:SetValue(0)
    ApplyBackdrop(bar, self.colors.bgDeep, self.colors.borderSoft)
    bar.value = self:CreateText(bar, 9, self.colors.text, "CENTER")
    bar.value:SetPoint("CENTER")
    return bar
end

function WN:SetProgress(bar, current, maximum, label)
    current = self:SafeNumber(current, 0) or 0
    maximum = self:SafeNumber(maximum, 0) or 0
    if maximum <= 0 then
        bar:SetMinMaxValues(0, 1)
        bar:SetValue(0)
    else
        bar:SetMinMaxValues(0, maximum)
        bar:SetValue(math.min(current, maximum))
    end
    if bar.value then bar.value:SetText(label or (current .. "/" .. maximum)) end
end

function WN:CreateIconRow(parent, width, height)
    local row = CreateFrame("Button", nil, parent, "BackdropTemplate")
    row:SetSize(width, height)
    ApplyBackdrop(row, self.colors.panel, self.colors.borderSoft)
    row.icon = row:CreateTexture(nil, "ARTWORK")
    row.icon:SetSize(height - 10, height - 10)
    row.icon:SetPoint("LEFT", 6, 0)
    row.name = self:CreateText(row, 11, self.colors.text, "LEFT")
    row.name:SetPoint("LEFT", row.icon, "RIGHT", 8, 6)
    row.name:SetPoint("RIGHT", -8, 6)
    row.meta = self:CreateText(row, 9, self.colors.muted, "LEFT")
    row.meta:SetPoint("LEFT", row.icon, "RIGHT", 8, -9)
    row.meta:SetPoint("RIGHT", -8, -9)
    row.right = self:CreateText(row, 10, self.colors.text, "RIGHT")
    row.right:SetPoint("RIGHT", -8, 0)
    row.right:SetWidth(90)
    row:SetScript("OnEnter", function(self) self:SetBackdropColor(unpack(WN.colors.panelHover)) end)
    row:SetScript("OnLeave", function(self) self:SetBackdropColor(unpack(WN.colors.panel)) end)
    return row
end

function WN:CreateSectionHeader(parent)
    parent.headerTitle = self:CreateText(parent, 20, self.colors.text, "LEFT")
    parent.headerTitle:SetPoint("TOPLEFT", 2, -2)
    parent.headerSub = self:CreateText(parent, 10, self.colors.muted, "LEFT")
    parent.headerSub:SetPoint("TOPLEFT", parent.headerTitle, "BOTTOMLEFT", 0, -3)
    parent.headerSub:SetPoint("RIGHT", -4, 0)
end

function WN:CreateScrollArea(parent, left, top, right, bottom)
    local scroll = CreateFrame("ScrollFrame", nil, parent, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", left, top)
    scroll:SetPoint("BOTTOMRIGHT", right, bottom)
    local child = CreateFrame("Frame", nil, scroll)
    child:SetSize(1, 1)
    scroll:SetScrollChild(child)
    scroll.child = child
    return scroll, child
end

function WN:CreateMainFrame()
    if self.frame then return end

    local frame = CreateFrame("Frame", "WoWNextMainFrame", UIParent, "BackdropTemplate")
    frame:SetSize(1080, 700)
    frame:SetPoint("CENTER", -100, 0)
    frame:SetFrameStrata("HIGH")
    frame:SetClampedToScreen(true)
    frame:SetMovable(true)
    frame:EnableMouse(true)
    frame:RegisterForDrag("LeftButton")
    ApplyBackdrop(frame, self.colors.bg, self.colors.border)
    frame:Hide()
    self.frame = frame

    -- Let WoW close the main window with ESC without capturing normal gameplay keys.
    if type(UISpecialFrames) == "table" then
        local registered = false
        for _, frameName in ipairs(UISpecialFrames) do
            if frameName == "WoWNextMainFrame" then registered = true; break end
        end
        if not registered then table.insert(UISpecialFrames, "WoWNextMainFrame") end
    end

    frame:SetScript("OnDragStart", function(self)
        if (WN.editorMode or not WN.db.settings.lockWindow) and not WN.minimized then self:StartMoving() end
    end)
    frame:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        WN:SaveMainPosition()
    end)
    frame:SetScript("OnShow", function()
        if WN.tracker and WN.db.settings.showTracker then WN.tracker:Show() end
        if WN.clockTicker and type(WN.clockTicker.Cancel) == "function" then WN.clockTicker:Cancel() end
        WN.clockTicker = C_Timer.NewTicker(1, function()
            if WN.frame and WN.frame:IsShown() and WN.frame.top then WN.frame.top.clock:SetText(WN:GetNowText()) end
        end)
        WN:RefreshAll()
    end)
    frame:SetScript("OnHide", function()
        if WN.tracker then WN.tracker:Hide() end
        if WN.clockTicker and type(WN.clockTicker.Cancel) == "function" then WN.clockTicker:Cancel() end
        WN.clockTicker = nil
        WN:HideHoverTooltip()
    end)

    local top = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    top:SetPoint("TOPLEFT", 0, 0)
    top:SetPoint("TOPRIGHT", 0, 0)
    top:SetHeight(62)
    ApplyBackdrop(top, self.colors.bgDeep, self.colors.borderSoft)
    frame.top = top
    top.glow = top:CreateTexture(nil, "BACKGROUND")
    top.glow:SetPoint("TOPLEFT", 0, 0)
    top.glow:SetPoint("BOTTOMRIGHT", 0, 0)
    top.glow:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.12)
    top.accentLine = top:CreateTexture(nil, "ARTWORK")
    top.accentLine:SetPoint("BOTTOMLEFT", 0, 0)
    top.accentLine:SetPoint("BOTTOMRIGHT", 0, 0)
    top.accentLine:SetHeight(2)
    top.accentLine:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.82)
    top.logo = top:CreateTexture(nil, "ARTWORK")
    top.logo:SetSize(54, 54)
    top.logo:SetPoint("LEFT", 14, 0)
    top.logo:SetTexture("Interface\\AddOns\\WoWNext\\Assets\\WoWNextCurseLogo.tga")

    top.title = self:CreateText(top, 13, self.colors.text, "LEFT")
    top.title:SetPoint("TOPLEFT", top.logo, "TOPRIGHT", 10, -4)
    top.title:SetText("WOWNEXT")
    top.version = self:CreateText(top, 9, self.colors.muted, "LEFT")
    top.version:SetPoint("LEFT", top.title, "RIGHT", 8, 0)
    top.version:SetText("v" .. self.version)
    top.season = self:CreateText(top, 10, self.colors.accent, "LEFT")
    top.season:SetPoint("TOPLEFT", top.title, "BOTTOMLEFT", 0, -3)
    top.season:SetText(self:T("THEME_ULATEK"))

    -- Build the right side of the header as an anchored chain.  The old
    -- absolute offsets allowed the clock text to run underneath the minimize
    -- button, which clipped the final digit at every UI scale.
    top.close = self:CreateButton(top, 28, 26, "×", function() WN.frame:Hide() end)
    top.close:SetPoint("RIGHT", -10, 0)

    top.minimize = self:CreateButton(top, 28, 26, "−", function() WN:ToggleMinimize() end)
    top.minimize:SetPoint("RIGHT", top.close, "LEFT", -6, 0)

    top.clockArea = CreateFrame("Frame", nil, top)
    top.clockArea:SetSize(78, 26)
    top.clockArea:SetPoint("RIGHT", top.minimize, "LEFT", -8, 0)

    top.clockIcon = self:CreateText(top.clockArea, 14, self.colors.muted, "CENTER")
    top.clockIcon:SetSize(18, 20)
    top.clockIcon:SetPoint("LEFT", 0, 0)
    top.clockIcon:SetText("◷")

    top.clock = self:CreateText(top.clockArea, 11, self.colors.text, "RIGHT")
    top.clock:SetPoint("LEFT", top.clockIcon, "RIGHT", 4, 0)
    top.clock:SetPoint("RIGHT", 0, 0)
    top.clock:SetHeight(20)

    top.search = CreateFrame("EditBox", nil, top, "InputBoxTemplate")
    top.search:SetSize(270, 28)
    top.search:SetPoint("RIGHT", top.clockArea, "LEFT", -12, 0)
    top.search:SetAutoFocus(false)
    top.search:SetMaxLetters(80)
    top.search:SetText("")
    top.search:SetScript("OnEnterPressed", function(box) WN:SearchNavigate(box:GetText()); box:ClearFocus() end)
    top.search:SetScript("OnEscapePressed", function(box) box:ClearFocus() end)
    top.search:SetScript("OnEditFocusGained", function(box) if box:GetText()==WN:T("SEARCH_HINT") then box:SetText("") end; box:SetTextColor(unpack(WN.colors.text)) end)
    top.search:SetScript("OnEditFocusLost", function(box) if box:GetText()=="" then box:SetText(WN:T("SEARCH_HINT")); box:SetTextColor(unpack(WN.colors.muted)) end end)
    top.search:SetText(self:T("SEARCH_HINT")); top.search:SetTextColor(unpack(self.colors.muted))

    local nav = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    nav:SetPoint("TOPLEFT", 0, -62)
    nav:SetPoint("BOTTOMLEFT", 0, 0)
    nav:SetWidth(184)
    ApplyBackdrop(nav, self.colors.bgDeep, self.colors.borderSoft)
    frame.nav = nav
    nav.tint = nav:CreateTexture(nil, "BACKGROUND")
    nav.tint:SetAllPoints()
    nav.tint:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.018)
    nav.buttons = {}

    local navKeys = {
        overview = "OVERVIEW", planner = "PLANNER", character = "CHARACTER", gear = "GEAR", activities = "ACTIVITIES",
        endgame = "ENDGAME", alts = "ALTS", collections = "COLLECTIONS", tools = "TOOLS", settings = "SETTINGS",
    }
    for index, section in ipairs(self.sectionOrder) do
        local button = CreateFrame("Button", nil, nav, "BackdropTemplate")
        button:SetPoint("TOPLEFT", 0, -(10 + (index - 1) * 43))
        button:SetSize(184, 41)
        ApplyBackdrop(button, {0,0,0,0}, {0,0,0,0})
        button.accent = button:CreateTexture(nil, "ARTWORK")
        button.accent:SetWidth(3)
        button.accent:SetPoint("TOPLEFT", 0, 0)
        button.accent:SetPoint("BOTTOMLEFT", 0, 0)
        button.accent:SetColorTexture(unpack(self.colors.accent))
        button.accent:Hide()
        button.icon = button:CreateTexture(nil, "ARTWORK")
        button.icon:SetSize(19, 19)
        button.icon:SetPoint("LEFT", 17, 0)
        button.icon:SetTexture(self.icons[section])
        button.label = self:CreateText(button, 11, self.colors.text, "LEFT")
        button.label:SetPoint("LEFT", 54, 0)
        button.label:SetText(self:T(navKeys[section]))
        button.section = section
        button:SetScript("OnClick", function(self) WN:ShowSection(self.section) end)
        button:SetScript("OnEnter", function(self)
            if not self.selected then self:SetBackdropColor(unpack(WN.colors.panel)) end
        end)
        button:SetScript("OnLeave", function(self)
            if not self.selected then self:SetBackdropColor(0,0,0,0) end
        end)
        nav.buttons[section] = button
    end

    nav.footer = self:CreateText(nav, 9, self.colors.faint, "LEFT")
    nav.footer:SetPoint("BOTTOMLEFT", 16, 14)
    nav.footer:SetText(self.seasonName or "Midnight • Curse of Ula-Tek")

    local profile = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    profile:SetPoint("TOPLEFT", 194, -74)
    profile:SetPoint("TOPRIGHT", -12, -74)
    profile:SetHeight(90)
    ApplyBackdrop(profile, self.colors.panel, self.colors.borderSoft)
    frame.profile = profile
    profile.tint = profile:CreateTexture(nil, "BACKGROUND")
    profile.tint:SetAllPoints()
    profile.tint:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.018)

    profile.portraitFrame = CreateFrame("Frame", nil, profile, "BackdropTemplate")
    profile.portraitFrame:SetSize(66, 66)
    profile.portraitFrame:SetPoint("LEFT", 8, 0)
    ApplyBackdrop(profile.portraitFrame, self.colors.bgDeep, self.colors.accentDim)
    profile.portrait = profile.portraitFrame:CreateTexture(nil, "ARTWORK")
    profile.portrait:SetPoint("TOPLEFT", 3, -3)
    profile.portrait:SetPoint("BOTTOMRIGHT", -3, 3)

    profile.name = self:CreateText(profile, 16, self.colors.text, "LEFT")
    profile.name:SetPoint("TOPLEFT", 84, -15)
    profile.meta = self:CreateText(profile, 10, self.colors.muted, "LEFT")
    profile.meta:SetPoint("TOPLEFT", profile.name, "BOTTOMLEFT", 0, -5)
    profile.meta:SetWidth(265)

    profile.metrics = {}
    local metricKeys = { "HEALTH", "POWER", "CRIT", "HASTE", "MASTERY", "VERSATILITY" }
    for i, key in ipairs(metricKeys) do
        local metric = CreateFrame("Frame", nil, profile)
        metric:SetSize(92, 58)
        metric:SetPoint("LEFT", 345 + (i - 1) * 87, 0)
        metric.value = self:CreateText(metric, 12, self.colors.text, "CENTER")
        metric.value:SetPoint("TOP", 0, -9)
        metric.label = self:CreateText(metric, 8, self.colors.muted, "CENTER")
        metric.label:SetPoint("TOP", metric.value, "BOTTOM", 0, -4)
        metric.label:SetText(self:T(key))
        profile.metrics[key] = metric
        if i > 1 then
            metric.sep = metric:CreateTexture(nil, "BACKGROUND")
            metric.sep:SetColorTexture(unpack(self.colors.borderSoft))
            metric.sep:SetWidth(1)
            metric.sep:SetPoint("TOPLEFT", -1, -8)
            metric.sep:SetPoint("BOTTOMLEFT", -1, 8)
        end
    end

    local pages = CreateFrame("Frame", nil, frame)
    pages:SetPoint("TOPLEFT", 194, -174)
    pages:SetPoint("BOTTOMRIGHT", -12, 12)
    frame.pages = pages
    frame.pageTint = frame:CreateTexture(nil, "BACKGROUND")
    frame.pageTint:SetPoint("TOPLEFT", pages, "TOPLEFT", -8, 0)
    frame.pageTint:SetPoint("BOTTOMRIGHT", pages, "BOTTOMRIGHT", 0, 0)
    frame.pageTint:SetColorTexture(self.colors.accent[1], self.colors.accent[2], self.colors.accent[3], 0.015)

    -- The old bottom FPS/latency bar was removed in 1.1.8.
    frame.status = nil

    self:CreateOverviewSection(pages)
    self:CreatePlannerSection(pages)
    self:CreateCharacterSection(pages)
    self:CreateGearSection(pages)
    self:CreateActivitiesSection(pages)
    self:CreateEndgameSection(pages)
    self:CreateAltsSection(pages)
    self:CreateCollectionsSection(pages)
    self:CreateToolsSection(pages)
    self:CreateSettingsSection(pages)
    self:CreateTracker()
    self:CreateMinimapButton()
    self:CreateHUDWidgets()
    self:RestorePositions()
    self:ShowSection(self.db.activeSection or "overview", true)
    self:ApplySettings()

end

function WN:UpdateProfileHeader(data)
    if not self.frame or not self.frame.profile then return end
    local profile = self.frame.profile
    data = data or self:GetCharacterSummary()
    if type(SetPortraitTexture) == "function" then self:SafeCall(SetPortraitTexture, profile.portrait, "player") end
    profile.name:SetText(data.name or "?")
    local classSpec = data.specName or self:T("UNKNOWN_SPEC")
    local zone = data.zone ~= "" and data.zone or data.realm
    profile.meta:SetText(self:T("LEVEL_SPEC", data.level or 0, classSpec, zone or ""))
    profile.metrics.HEALTH.value:SetText(self:FormatNumber(data.health))
    profile.metrics.POWER.value:SetText(self:FormatNumber(data.power))
    profile.metrics.CRIT.value:SetText(self:FormatPercent(data.criticalStrike))
    profile.metrics.HASTE.value:SetText(self:FormatPercent(data.haste))
    profile.metrics.MASTERY.value:SetText(self:FormatPercent(data.mastery))
    profile.metrics.VERSATILITY.value:SetText(self:FormatPercent(data.versatility))
end

function WN:ShowSection(section, silent)
    if not self.sections[section] then section = "overview" end
    self.activeSection = section
    if self.db then self.db.activeSection = section end
    for id, panel in pairs(self.sections) do panel:SetShown(id == section) end
    if self.frame and self.frame.nav then
        for id, button in pairs(self.frame.nav.buttons) do
            local selected = id == section
            button.selected = selected
            button.accent:SetShown(selected)
            if selected then
                button:SetBackdropColor(self.colors.accentDim[1], self.colors.accentDim[2], self.colors.accentDim[3], 0.48)
                button.icon:SetVertexColor(unpack(self.colors.accent))
            else
                button:SetBackdropColor(0,0,0,0)
                button.icon:SetVertexColor(0.72,0.82,0.86,1)
            end
        end
    end
    if not silent then self:RefreshActiveSection() end
end

function WN:RefreshActiveSection()
    local data = self:RunModule("data", function() return self:GetOverviewData() end)
    if not data then return end
    self.cachedOverview = data
    self:UpdateProfileHeader(data.character)
    if self.activeSection == "overview" then self:RunModule("overview", function() self:UpdateOverviewSection(data) end)
    elseif self.activeSection == "planner" then self:RunModule("planner", function() self:UpdatePlannerSection(data) end)
    elseif self.activeSection == "character" then self:RunModule("character", function() self:UpdateCharacterSection(data) end)
    elseif self.activeSection == "gear" then self:RunModule("gear", function() self:UpdateGearSection(data) end)
    elseif self.activeSection == "activities" then self:RunModule("activities", function() self:UpdateActivitiesSection(data) end)
    elseif self.activeSection == "endgame" then self:RunModule("endgame", function() self:UpdateEndgameSection(data) end)
    elseif self.activeSection == "alts" then self:RunModule("alts", function() self:UpdateAltsSection(data) end)
    elseif self.activeSection == "collections" then self:RunModule("collections", function() self:UpdateCollectionsSection(data) end)
    elseif self.activeSection == "tools" then self:RunModule("tools", function() self:UpdateToolsSection(data) end)
    elseif self.activeSection == "settings" then self:RunModule("settings", function() self:UpdateSettingsSection() end) end
    self:UpdateTracker(data)
end

function WN:RefreshAll()
    if not self.frame then return end
    self.frame.top.clock:SetText(self:GetNowText())
    self:RefreshActiveSection()
    if self.cachedOverview then
        self:SaveCharacterSnapshot(self.cachedOverview)
        self:UpdateHUDWidgets()
    end
end

function WN:UpdateStatusBar()
    -- Kept as a compatibility no-op for old profiles and external calls.
end

function WN:ToggleMinimize()
    if not self.frame then return end
    self.minimized = not self.minimized
    if self.minimized then
        self.frame.nav:Hide()
        self.frame.profile:Hide()
        self.frame.pages:Hide()
        self.frame:SetHeight(62)
        self.frame.top.minimize.label:SetText("+")
    else
        self.frame:SetHeight(700)
        self.frame.nav:Show()
        self.frame.profile:Show()
        self.frame.pages:Show()
        self.frame.top.minimize.label:SetText("−")
    end
end

function WN:Toggle()
    if not self.frame then self:CreateMainFrame() end
    self.frame:SetShown(not self.frame:IsShown())
end

function WN:OpenSettings()
    if not self.frame then self:CreateMainFrame() end
    self.frame:Show()
    self:ShowSection("settings")
end

function WN:OpenTool(action)
    if InCombatLockdown and InCombatLockdown() then
        self:Print(self:T("COMBAT_BLOCKED"))
        return
    end
    if action == "group" then
        if type(PVEFrame_ToggleFrame) == "function" then PVEFrame_ToggleFrame() end
    elseif action == "calendar" then
        if type(ToggleCalendar) == "function" then ToggleCalendar() elseif type(Calendar_Toggle) == "function" then Calendar_Toggle() end
    elseif action == "auction" then
        if AuctionHouseFrame and AuctionHouseFrame:IsShown() then
            if type(ToggleFrame) == "function" then ToggleFrame(AuctionHouseFrame) end
        else
            self:Print(self:T("AUCTION_NEAR_NPC"))
        end
    elseif action == "map" or action == "locations" then
        if type(ToggleWorldMap) == "function" then ToggleWorldMap() end
    elseif action == "professions" or action == "workorders" then
        if type(ProfessionsFrame_LoadUI) == "function" then ProfessionsFrame_LoadUI() end
        if ProfessionsFrame and type(ToggleFrame) == "function" then ToggleFrame(ProfessionsFrame) end
    elseif action == "collections" then
        if type(ToggleCollectionsJournal) == "function" then ToggleCollectionsJournal() end
    elseif action == "mounts" then
        if type(ToggleCollectionsJournal) == "function" then ToggleCollectionsJournal(1) end
    elseif action == "pets" then
        if type(ToggleCollectionsJournal) == "function" then ToggleCollectionsJournal(2) end
    elseif action == "toys" then
        if type(ToggleCollectionsJournal) == "function" then ToggleCollectionsJournal(3) end
    elseif action == "appearances" then
        if type(ToggleCollectionsJournal) == "function" then ToggleCollectionsJournal(5) end
    elseif action == "achievements" then
        if type(ToggleAchievementFrame) == "function" then ToggleAchievementFrame() end
    elseif action == "character" then
        if type(ToggleCharacter) == "function" then ToggleCharacter("PaperDollFrame") end
    elseif action == "talents" then
        if type(PlayerSpellsUtil) == "table" and type(PlayerSpellsUtil.ToggleFrame) == "function" then
            PlayerSpellsUtil.ToggleFrame()
        elseif type(ToggleTalentFrame) == "function" then
            ToggleTalentFrame()
        end
    elseif action == "adventure" then
        if type(ToggleEncounterJournal) == "function" then ToggleEncounterJournal() elseif type(PVEFrame_ToggleFrame) == "function" then PVEFrame_ToggleFrame() end
    elseif action == "vault" then
        if type(WeeklyRewards_LoadUI) == "function" then WeeklyRewards_LoadUI() end
        if WeeklyRewardsFrame and type(ToggleFrame) == "function" then ToggleFrame(WeeklyRewardsFrame) end
    end
end

function WN:SaveMainPosition()
    if not self.db or not self.frame then return end
    if self.editorMode and self.SnapFramePosition then self:SnapFramePosition(self.frame, 10) end
    local point, _, relativePoint, x, y = self.frame:GetPoint(1)
    self.db.positions.main = { point = point, relativePoint = relativePoint, x = x, y = y }
end

function WN:RestorePositions()
    if not self.db or not self.frame then return end
    local pos = self.db.positions and self.db.positions.main
    if type(pos) == "table" and pos.point then
        self.frame:ClearAllPoints()
        self.frame:SetPoint(pos.point, UIParent, pos.relativePoint or pos.point, pos.x or 0, pos.y or 0)
    end
    if self.tracker then self:RestoreTrackerPosition() end
end

function WN:ResetPositions()
    if not self.db then return end
    self.db.positions = {}
    if self.frame then
        self.frame:ClearAllPoints()
        self.frame:SetPoint("CENTER", UIParent, "CENTER", -100, 0)
    end
    if self.tracker then
        self.tracker:ClearAllPoints()
        self.tracker:SetPoint("TOPLEFT", self.frame, "TOPRIGHT", 14, 0)
    end
end

function WN:CreateMinimapButton()
    if self.minimapButton then return end
    local button = CreateFrame("Button", "WoWNextMinimapButton", Minimap, "BackdropTemplate")
    button:SetSize(34, 34)
    button:SetFrameStrata("MEDIUM")
    ApplyBackdrop(button, self.colors.bgDeep, self.colors.accentDim)
    button.icon = button:CreateTexture(nil, "ARTWORK")
    button.icon:SetPoint("TOPLEFT", 4, -4)
    button.icon:SetPoint("BOTTOMRIGHT", -4, 4)
    button.icon:SetTexture("Interface\\Icons\\INV_Misc_Gem_01")
    button.icon:SetVertexColor(unpack(self.colors.accent))
    button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    button:RegisterForDrag("LeftButton")
    button:SetScript("OnClick", function(_, mouseButton)
        if mouseButton == "RightButton" then WN:ToggleMinimapDashboard() else WN:Toggle() end
    end)
    button:SetScript("OnEnter", function(self)
        local tip = WN:PrepareHoverTooltip(self, "ANCHOR_LEFT")
        tip:SetText("WoWNext", 1, 1, 1)
        tip:AddLine(WN:T("TOOLTIP_OPEN"), 0.7, 0.85, 0.9)
        tip:AddLine(WN:T("MINIMAP_TOOLTIP_RIGHT"), 0.7, 0.85, 0.9)
        if WN.cachedOverview then
            tip:AddLine(" ")
            tip:AddLine(string.format("iLvl %.1f   M+ %s", WN.cachedOverview.character.equippedItemLevel or 0, WN:FormatNumber(WN.cachedOverview.character.mythicRating)), 0.8, 0.9, 0.9)
            tip:AddLine(string.format("%s %d/%d", WN:T("GREAT_VAULT"), WN.cachedOverview.weekly.completedThresholds or 0, WN.cachedOverview.weekly.totalThresholds or 0), 0.8, 0.9, 0.9)
        end
        tip:Show()
    end)
    button:SetScript("OnLeave", function() WN:HideHoverTooltip() end)
    local function UpdateMinimapDrag(self)
        local mx, my = Minimap:GetCenter()
        local scale = Minimap:GetEffectiveScale()
        local cx, cy = GetCursorPosition()
        cx, cy = cx / scale, cy / scale
        local angle = math.atan2(cy - my, cx - mx)
        WN.db.minimapAngle = angle
        WN:UpdateMinimapPosition()
    end
    button:SetScript("OnDragStart", function(self)
        self.dragging = true
        self:SetScript("OnUpdate", UpdateMinimapDrag)
    end)
    button:SetScript("OnDragStop", function(self)
        self.dragging = false
        self:SetScript("OnUpdate", nil)
    end)
    self.minimapButton = button
    self:UpdateMinimapPosition()
end

function WN:UpdateMinimapPosition()
    if not self.minimapButton or not Minimap then return end
    local angle = self.db and self.db.minimapAngle or 2.35
    local radius = (Minimap:GetWidth() / 2) + 8
    self.minimapButton:ClearAllPoints()
    self.minimapButton:SetPoint("CENTER", Minimap, "CENTER", math.cos(angle) * radius, math.sin(angle) * radius)
end

function WN:ApplySettings()
    if not self.db then return end
    local settings = self.db.settings
    if self.frame then
        self.frame:SetScale((settings.uiScale or 100) / 100)
        self.frame:SetAlpha((settings.opacity or 100) / 100)
    end
    if self.tracker then
        self.tracker:SetScale((settings.uiScale or 100) / 100)
        self.tracker:SetAlpha((settings.opacity or 100) / 100)
        self.tracker:SetShown(self.frame and self.frame:IsShown() and settings.showTracker ~= false)
    end
    if self.minimapButton then self.minimapButton:SetShown(settings.showMinimap ~= false) end
    if self.frame and self.frame.top and self.frame.top.search then self.frame.top.search:SetShown(settings.showSearch ~= false) end
    if self.widgets then
        local safe = settings.safeMode == true
        local map = { performance="widgetPerformance", character="widgetCharacter", currency="widgetCurrency", weekly="widgetWeekly", bags="widgetBags" }
        for id, widget in pairs(self.widgets) do widget:SetShown(not safe and settings[map[id]] == true) end
        if self.UpdateHUDTicker then self:UpdateHUDTicker() end
    end
end
