local addonName, NS = ...

-- ============================================================================
-- VAULTSKINS: SKIN COORDINATOR
-- ============================================================================
local Skin = NS.Skin
local Shared = NS.SkinShared
local ApplyBackdrop = Shared.ApplyBackdrop
local ApplyStyledBackdrop = Shared.ApplyStyledBackdrop
local BlendColor = Shared.BlendColor
local CancelOwnedTimer = Shared.CancelOwnedTimer
local CancelOwnedTimers = Shared.CancelOwnedTimers
local CreateBackdropFrame = Shared.CreateBackdropFrame
local EnsureReadableColor = Shared.EnsureReadableColor
local GetEdgeOnlyBackdrop = Shared.GetEdgeOnlyBackdrop
local GetEffectiveThemeStyle = Shared.GetEffectiveThemeStyle
local GetFrameEffectProfile = Shared.GetFrameEffectProfile
local GetFrameEffectColor = Shared.GetFrameEffectColor
local HideVisual = Shared.HideVisual
local IsOwnedTimerPending = Shared.IsOwnedTimerPending
local RecordStabilityMetric = Shared.RecordStabilityMetric
local SafeCall = Shared.SafeCall
local ScaleColor = Shared.ScaleColor
local ScheduleOwnedTimer = Shared.ScheduleOwnedTimer
local SetColor = Shared.SetColor
local SetVertexColor = Shared.SetVertexColor
local ShowVisual = Shared.ShowVisual
local SuppressNativeVaultArtwork = Shared.SuppressNativeVaultArtwork

-- ======================= NATIVE SELECTION AND SCALING =======================
local function HideNativeSelectionDecorations(activity)
    local callback = Shared.HideNativeSelectionDecorations
    if callback then return callback(activity) end
end

function Skin:ApplyScale(ignoreLayout)
    local frame = WeeklyRewardsFrame
    if not frame then
        return 1
    end

    local desired = VaultSkinsDB and tonumber(VaultSkinsDB.scale) or 1
    desired = math.max(0.75, math.min(1.25, desired))
    local layoutMultiplier = (not ignoreLayout and NS.Layout and NS.Layout:GetScaleMultiplier()) or 1
    desired = math.max(0.62, math.min(1.35, desired * layoutMultiplier))
    local effective = desired

    if VaultSkinsDB and VaultSkinsDB.autoFit ~= false and UIParent then
        local frameWidth = frame:GetWidth() or 0
        local frameHeight = frame:GetHeight() or 0
        local screenWidth = UIParent:GetWidth() or 0
        local screenHeight = UIParent:GetHeight() or 0
        if frameWidth > 0 and frameHeight > 0 and screenWidth > 0 and screenHeight > 0 then
            local horizontalFit = (screenWidth - 72) / frameWidth
            local verticalFit = (screenHeight - 72) / frameHeight
            effective = math.min(desired, horizontalFit, verticalFit)
            effective = math.max(0.62, effective)
        end
    end

    frame:SetScale(effective)
    self.effectiveScale = effective
    return effective
end

-- ========================== THEME APPLY SCHEDULER ===========================
function Skin:RequestThemeApply(reason, delay)
    if IsOwnedTimerPending("rarity-refresh") then
        self.rewardRarityToken = (self.rewardRarityToken or 0) + 1
        CancelOwnedTimer("rarity-refresh")
        RecordStabilityMetric("skippedDuplicateRefreshes")
    end
    self.applyRequestToken = (self.applyRequestToken or 0) + 1
    local token = self.applyRequestToken
    self.lastApplyReason = tostring(reason or "unspecified")
    RecordStabilityMetric("themeApplyRequests")
    ScheduleOwnedTimer("theme-apply", delay or 0.03, function()
        if token ~= Skin.applyRequestToken then
            return
        end
        local frame = WeeklyRewardsFrame
        if frame and frame:IsShown() then
            SafeCall("coalesced theme apply", Skin.ApplyTheme, Skin, false)
        end
    end)
end

function Skin:Repair()
    if not WeeklyRewardsFrame then
        if C_AddOns and C_AddOns.LoadAddOn then
            pcall(C_AddOns.LoadAddOn, "Blizzard_WeeklyRewards")
        elseif LoadAddOn then
            pcall(LoadAddOn, "Blizzard_WeeklyRewards")
        end
    end

    CancelOwnedTimers("manual repair")
    self:StopOpeningAnimation(true)
    self.sessionFallback = false
    self.sessionDisabledFeatures = {}
    self:ResetErrorDiagnostics()
    if WeeklyRewardsFrame and WeeklyRewardsFrame.Activities then
        for _, activity in ipairs(WeeklyRewardsFrame.Activities) do
            activity.VaultSkinsBuildFailed = nil
            activity.VaultSkinsBuildFailures = nil
            activity.VaultSkinsBuildInProgress = nil
            activity.VaultSkinsBuilding = nil
        end
    end
    self.applyRequestToken = (self.applyRequestToken or 0) + 1
    if NS.Compatibility then NS.Compatibility:Refresh() end
    if NS.ThemeEngine and NS.ThemeEngine.ClearCache then NS.ThemeEngine:ClearCache() end
    if WeeklyRewardsFrame then
        if not self.initialized then self:Initialize() end
        SafeCall("manual repair", self.ApplyTheme, self, false)
        self:ApplyScale()
    end
    if NS.Options then NS.Options:Refresh() end
    print("|cff9f78ffVaultSkins:|r Vault UI repaired and reapplied.")
end

-- ========================= CONCESSION FRAME STYLING =========================
local function SetFontStringColors(readable, outline, ...)
    for index = 1, select("#", ...) do
        local region = select(index, ...)
        if region and region.GetObjectType and region:GetObjectType() == "FontString" then
            region:SetTextColor(readable.r, readable.g, readable.b, readable.a or 1)
            local font, size, flags = region:GetFont()
            if font then region:SetFont(font, size or 12, outline or flags or "OUTLINE") end
        end
    end
end

local function SetAllFontStringsColor(frame, color, outline)
    if not frame or not frame.GetRegions then return end
    local readable = EnsureReadableColor(color, 0.82)
    SetFontStringColors(readable, outline, frame:GetRegions())
end


local function IsBackdropFrame(value)
    return value and type(value.ClearAllPoints) == "function" and type(value.SetPoint) == "function" and type(value.SetBackdrop) == "function"
end

local function EnsureConcessionBackdrop(owner, fieldName)
    if not owner or not owner.GetFrameLevel then return nil end
    local backdrop = owner[fieldName]
    if not IsBackdropFrame(backdrop) then
        backdrop = CreateBackdropFrame(owner, 2, -1)
        owner[fieldName] = backdrop
    end
    if not IsBackdropFrame(backdrop) then return nil end
    backdrop:ClearAllPoints()
    backdrop:SetPoint("TOPLEFT", owner, "TOPLEFT", 2, -2)
    backdrop:SetPoint("BOTTOMRIGHT", owner, "BOTTOMRIGHT", -2, 2)
    return backdrop
end

local function HideChildConcessionBackdrops(...)
    for index = 1, select("#", ...) do
        local child = select(index, ...)
        if child and child.VaultSkinsConcessionBackdrop then
            child.VaultSkinsConcessionBackdrop:Hide()
        end
    end
end

local function StyleConcessionChildren(theme, readable, ...)
    for index = 1, select("#", ...) do
        local child = select(index, ...)
        if child then
            if child.VaultSkinsConcessionBackdrop then
                child.VaultSkinsConcessionBackdrop:Hide()
            end
            if child.GetObjectType and child:GetObjectType() == "Button" and child.Text then
                child.Text:SetTextColor(readable.r, readable.g, readable.b, 1)
            end
            SetAllFontStringsColor(child, theme.text)
        end
    end
end

function Skin:StyleConcessionFrame(frame, theme)
    if not frame or not theme then return end

    local candidates = {
        frame.ConcessionFrame,
        frame.ConcessionContainer,
        frame.ChooseConcessionFrame,
        frame.CurrencyFrame,
    }

    local selectedBox, selectedWidth = nil, 0
    local seen = {}
    for _, box in ipairs(candidates) do
        if box and not seen[box] then
            seen[box] = true
            local width = box.GetWidth and tonumber(box:GetWidth()) or 0
            local height = box.GetHeight and tonumber(box:GetHeight()) or 0
            local wideEnough = width >= 260 and height <= 90 and width >= math.max(260, height * 3.0)
            if wideEnough and width > selectedWidth then
                selectedBox, selectedWidth = box, width
            end
        end
    end

    for box in pairs(seen) do
        if box.VaultSkinsConcessionBackdrop then
            box.VaultSkinsConcessionBackdrop:Hide()
        end
        if box.GetChildren then
            HideChildConcessionBackdrops(box:GetChildren())
        end
    end

    local box = selectedBox
    if not box or not box.GetChildren then
        return
    end
    if box.Background then HideVisual(box.Background) end

    local boxBackdrop = EnsureConcessionBackdrop(box, "VaultSkinsConcessionBackdrop")
    if boxBackdrop then
        boxBackdrop:Show()
        ApplyBackdrop(boxBackdrop, BlendColor(theme.panel, theme.reward, 0.28, 0.98), theme.accent, 2)
    end

    SetAllFontStringsColor(box, theme.text)
    local readable = EnsureReadableColor(theme.text, 0.82)
    StyleConcessionChildren(theme, readable, box:GetChildren())
end

-- ========================== MAIN THEME APPLICATION ==========================
function Skin:ApplyThemeUnsafe(animate)
    local frame = WeeklyRewardsFrame
    if not frame then return end
    if not self:IsEnabled() then
        self:RestoreOriginalUI(false)
        return
    end
    self:SetCustomVisualsShown(true)

    local theme = self:GetTheme()
    if not theme then
        return
    end

    local frameStyle = GetEffectiveThemeStyle(theme, "frameStyle")
    local skin = self:CreateMainSkin(frame)
    local frameBorderColor = theme.frameBorder or theme.border
    local frameAccentColor = theme.frameAccent or theme.accent
    local metal = frameBorderColor
    local silver = theme.silver or theme.text
    local minimalButtonSkin = NS.Compatibility and NS.Compatibility:UseMinimalButtonSkin()

    HideVisual(frame.Background)
    HideVisual(frame.BorderShadow)
    HideVisual(frame.Divider1)
    HideVisual(frame.Divider2)
    SuppressNativeVaultArtwork(frame)

    -- Blizzard reapplies its border atlas shortly after the frame opens.
    ScheduleOwnedTimer("native-vault-art-cleanup", 0.08, function()
        if frame and frame:IsShown() and Skin:IsEnabled() then
            SuppressNativeVaultArtwork(frame)
        end
    end)
    if frame.SelectRewardButton and frame.SelectRewardButton.Background then
        if minimalButtonSkin then
            ShowVisual(frame.SelectRewardButton.Background, 1)
        else
            HideVisual(frame.SelectRewardButton.Background)
        end
    end

    local outerShadowAlpha = frameStyle.outerShadowAlpha or 0.76
    ApplyBackdrop(skin.OuterShadow, {
        r = 0, g = 0, b = 0, a = outerShadowAlpha,
    }, {
        r = 0, g = 0, b = 0, a = math.min(1, outerShadowAlpha + 0.12),
    }, 6)

    ApplyStyledBackdrop(skin.Backdrop, theme.background, metal, frameStyle, frameStyle.edgeSize or 18)
    ApplyBackdrop(skin.MetalOuter, ScaleColor(theme.background, 0.72, 0.98), {
        r = silver.r, g = silver.g, b = silver.b, a = 0.58,
    }, 2)
    ApplyBackdrop(skin.Inner, {
        r = math.min(1, theme.background.r + 0.010),
        g = math.min(1, theme.background.g + 0.012),
        b = math.min(1, theme.background.b + 0.020),
        a = 0.90,
    }, {
        r = frameBorderColor.r,
        g = frameBorderColor.g,
        b = frameBorderColor.b,
        a = 0.28,
    }, 1)

    local headerInset = math.max(80, math.min(150, tonumber(frameStyle.headerBandInset) or 112))
    skin.HeaderBand:ClearAllPoints()
    skin.HeaderBand:SetPoint("TOPLEFT", frame, "TOPLEFT", headerInset, -19)
    skin.HeaderBand:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -headerInset, -19)
    skin.HeaderBand:SetHeight(math.max(82, math.min(112, tonumber(frameStyle.headerBandHeight) or 98)))

    local outerGlowInset = math.max(0, math.min(8, tonumber(frameStyle.outerGlowInset) or 3))
    skin.OpeningGlow:ClearAllPoints()
    skin.OpeningGlow:SetPoint("TOPLEFT", frame, "TOPLEFT", outerGlowInset, -outerGlowInset)
    skin.OpeningGlow:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -outerGlowInset, outerGlowInset)
    skin.OpeningGlow:SetBackdrop(GetEdgeOnlyBackdrop(math.max(2, math.min(10, tonumber(frameStyle.outerGlowEdgeSize) or 7))))
    skin.OpeningGlow:SetBackdropColor(0, 0, 0, 0)

    ApplyBackdrop(skin.HeaderBand, {
        r = theme.panel.r,
        g = theme.panel.g,
        b = theme.panel.b,
        a = frameStyle.headerBandAlpha or 0.64,
    }, {
        r = metal.r, g = metal.g, b = metal.b, a = 0.32,
    }, 1)

    ApplyStyledBackdrop(skin.CrestPlate, ScaleColor(theme.background, 0.74, 0.98), {
        r = silver.r,
        g = silver.g,
        b = silver.b,
        a = frameStyle.crestBorderAlpha or 0.72,
    }, frameStyle, 14)
    local frameEffect = GetFrameEffectProfile(theme)
    local crestEffectColor = GetFrameEffectColor(theme, frameEffect, "crestColor", "accent")
    local dividerEffectColor = GetFrameEffectColor(theme, frameEffect, "dividerColor", "glow")
    local topEffectColor = GetFrameEffectColor(theme, frameEffect, "topColor", "glow")
    local sideEffectColor = GetFrameEffectColor(theme, frameEffect, "sideColor", "glow")
    local bottomEffectColor = GetFrameEffectColor(theme, frameEffect, "bottomColor", "accent")
    local cornerEffectColor = GetFrameEffectColor(theme, frameEffect, "cornerColor", "metal")
    skin.CrestGlow:SetBackdropBorderColor(crestEffectColor.r, crestEffectColor.g, crestEffectColor.b, 1)

    skin.BackgroundTexture:SetVertexColor(theme.panel.r, theme.panel.g, theme.panel.b, 0.70)
    skin.BackgroundTexture:SetAlpha(frameStyle.backgroundTextureAlpha or 0.62)

    SetVertexColor(skin.TopGlow, topEffectColor)
    skin.TopGlow:SetAlpha(frameStyle.topGlowAlpha or 0.38)
    SetVertexColor(skin.CenterMist, theme.glow)
    skin.CenterMist:SetAlpha(frameStyle.centerMistAlpha or 0.10)

    SetColor(skin.TopAccent, silver)
    skin.TopAccent:SetAlpha(frameStyle.topAccentAlpha or 0.66)
    SetColor(skin.HeaderDivider, frameAccentColor)
    skin.HeaderDivider:SetAlpha(frameStyle.headerDividerAlpha or 0.72)
    SetVertexColor(skin.HeaderDividerGlow, dividerEffectColor)
    SetColor(skin.BottomAccent, metal)
    skin.BottomAccent:SetAlpha(frameStyle.bottomAccentAlpha or 0.52)

    SetVertexColor(skin.SideGlowLeft, sideEffectColor)
    SetVertexColor(skin.SideGlowRight, sideEffectColor)
    skin.SideGlowLeft:SetAlpha(frameStyle.sideGlowAlpha or 0.16)
    skin.SideGlowRight:SetAlpha(frameStyle.sideGlowAlpha or 0.16)
    local outerGlowColor = theme.outerGlow or frameAccentColor
    skin.OpeningGlow:SetBackdropBorderColor(outerGlowColor.r, outerGlowColor.g, outerGlowColor.b, 1)
    SetVertexColor(skin.FrameTraceTop, topEffectColor)
    SetVertexColor(skin.FrameTraceLeft, sideEffectColor)
    SetVertexColor(skin.FrameTraceRight, sideEffectColor)
    SetVertexColor(skin.FrameTraceBottom, bottomEffectColor)
    local sparklePrimary = GetFrameEffectColor(theme, frameEffect, "particlePrimary", "accent")
    local sparkleSecondary = GetFrameEffectColor(theme, frameEffect, "particleSecondary", "silver")
    for index, sparkle in ipairs(skin.FrameSparkles or {}) do
        SetVertexColor(sparkle, index % 2 == 0 and sparklePrimary or sparkleSecondary)
    end

    for _, corner in ipairs(skin.Corners) do
        SetColor(corner.Horizontal, cornerEffectColor)
        SetColor(corner.Vertical, cornerEffectColor)
        corner.Horizontal:SetAlpha(frameStyle.cornerAlpha or 0.62)
        corner.Vertical:SetAlpha(frameStyle.cornerAlpha or 0.62)
    end

    self:ApplyMainThemeArtwork(frame, skin, theme)
    SafeCall("theme background scene", self.ApplyBackgroundScene, self, theme, false)

    if frame.HeaderFrame and frame.HeaderFrame.Text then
        local headerText = EnsureReadableColor(theme.text, 0.84)
        frame.HeaderFrame.Text:SetTextColor(headerText.r, headerText.g, headerText.b, 1)
        local font = frame.HeaderFrame.Text:GetFont()
        frame.HeaderFrame.Text:SetFont(font or STANDARD_TEXT_FONT, 27, "OUTLINE")
        frame.HeaderFrame.Text:SetShadowColor(0, 0, 0, 0.95)
        frame.HeaderFrame.Text:SetShadowOffset(2, -2)
    end

    self:StyleRow(frame.RaidFrame)
    self:StyleRow(frame.MythicFrame)
    self:StyleRow(frame.PVPFrame)
    self:StyleRow(frame.WorldFrame)

    if frame.Activities then
        if NS.RewardRarity then
            self.rewardRaritySummary = NS.RewardRarity:AnalyzeActivities(frame.Activities)
        end
        for _, activity in ipairs(frame.Activities) do
            self:StyleActivity(activity)
        end
    end

    if frame.SelectRewardButton then
        if frame.SelectRewardButton.SetNormalFontObject and not minimalButtonSkin then
            frame.SelectRewardButton:SetNormalFontObject(GameFontNormalLarge)
        end
        if frame.SelectRewardButton.Text and not minimalButtonSkin then
            local buttonText = EnsureReadableColor(theme.text, 0.84)
            frame.SelectRewardButton.Text:SetTextColor(buttonText.r, buttonText.g, buttonText.b, 1)
        end
        if not frame.SelectRewardButton.VaultSkins then
            frame.SelectRewardButton.VaultSkins = CreateBackdropFrame(frame.SelectRewardButton, 2, -1)
        end
        frame.SelectRewardButton.VaultSkins:ClearAllPoints()
        frame.SelectRewardButton.VaultSkins:SetPoint("TOPLEFT", frame.SelectRewardButton, "TOPLEFT", 2, -2)
        frame.SelectRewardButton.VaultSkins:SetPoint("BOTTOMRIGHT", frame.SelectRewardButton, "BOTTOMRIGHT", -2, 2)
        frame.SelectRewardButton.VaultSkins:SetShown(not minimalButtonSkin)
        if not minimalButtonSkin then
            ApplyBackdrop(frame.SelectRewardButton.VaultSkins, BlendColor(theme.panel, theme.reward, 0.18, 0.96), { r = theme.border.r, g = theme.border.g, b = theme.border.b, a = 0.84 }, 2)
        end
    end

    SafeCall("style concession frame", self.StyleConcessionFrame, self, frame, theme)

    local themeButton = self:CreateThemeButton(frame)
    self:StyleThemeButton(themeButton)

    if NS.Layout then NS.Layout:ApplyCurrent(frame) end
    self:ApplyScale()
    frame:SetAlpha(1)

    if animate then
        self:PlayOpeningAnimation(true)
    elseif frame:IsShown() and not self.revealActive then
        ScheduleOwnedTimer("idle-after-apply", 0, function()
            if frame:IsShown() and Skin:IsEnabled() and not Skin.revealActive then Skin:StartFrameIdleAnimation() end
        end)
    end
end

function Skin:ApplyTheme(animate)
    if self.applyInProgress then
        self.applyAgain = true
        return false
    end

    self.applyInProgress = true
    RecordStabilityMetric("themeAppliesExecuted")
    local ok, result = pcall(self.ApplyThemeUnsafe, self, animate)
    self.applyInProgress = false

    if self.applyAgain then
        self.applyAgain = false
        self:RequestThemeApply("deferred reapply", 0)
    end

    if not ok then error(tostring(result), 0) end
    return result
end

-- =============================== SECURE HOOKS ===============================
function Skin:InstallHooks()
    if self.hooksInstalled then
        return
    end
    self.hooksInstalled = true

    if WeeklyRewardsMixin and WeeklyRewardsMixin.Refresh then
        hooksecurefunc(WeeklyRewardsMixin, "Refresh", function(frame)
            if NS.RewardRarity then NS.RewardRarity:Reset() end
            Skin.userSelectedActivity = nil
            if frame and frame:IsShown() then
                if Skin.pendingOpeningRevealPrivacy then
                    Skin:PrimeOpeningRevealPrivacy()
                end
                Skin:RequestThemeApply("vault refresh", 0.03)
                Skin:RequestRewardRarityRefresh(0.08)
                ScheduleOwnedTimer("refresh-clear-selection", 0.10, function()
                    if frame and frame:IsShown() and Skin:IsEnabled() and not Skin.userSelectedActivity then
                        SafeCall("clear auto selection after refresh", Skin.ClearAutomaticSelection, Skin)
                    end
                end)
            end
        end)
    end

    if WeeklyRewardsMixin and WeeklyRewardsMixin.UpdateSelection then
        hooksecurefunc(WeeklyRewardsMixin, "UpdateSelection", function(frame)
            ScheduleOwnedTimer("selection-refresh", 0, function()
                SafeCall("selection refresh", function()
                    Skin.pendingSelectedActivity = nil
                    Skin.pendingSelectionExpires = nil
                    if frame and frame.selectedActivity == nil then
                        Skin.userSelectedActivity = nil
                    elseif Skin.userSelectedActivity and frame and frame.selectedActivity and Skin.userSelectedActivity ~= frame.selectedActivity then
                        local found = false
                        if frame.Activities then
                            for _, activity in ipairs(frame.Activities) do
                                if activity == Skin.userSelectedActivity then found = true break end
                            end
                        end
                        if not found then Skin.userSelectedActivity = nil end
                    end
                    if not Skin:IsEnabled() then return end
                    if frame and frame.Activities then
                        for _, activity in ipairs(frame.Activities) do
                            Skin:UpdateActivityVisual(activity, activity:IsMouseOver())
                        end
                        if not Skin.userSelectedActivity then
                            for _, activity in ipairs(frame.Activities) do
                                if HideNativeSelectionDecorations then HideNativeSelectionDecorations(activity) end
                            end
                        end
                    end
                end)
            end)
        end)
    end

    if WeeklyRewardsActivityMixin and WeeklyRewardsActivityMixin.Refresh then
        hooksecurefunc(WeeklyRewardsActivityMixin, "Refresh", function(activity)
            if NS.RewardRarity then NS.RewardRarity:Invalidate(activity) end
            local key = "card-refresh:" .. tostring(activity)
            if not IsOwnedTimerPending("theme-apply") then
                ScheduleOwnedTimer(key, 0, function()
                    SafeCall("reward card refresh", Skin.StyleActivity, Skin, activity)
                end)
            else
                RecordStabilityMetric("skippedDuplicateRefreshes")
            end
            Skin:RequestRewardRarityRefresh(0.08)
        end)
    end

    if WeeklyRewardActivityItemMixin and WeeklyRewardActivityItemMixin.SetDisplayedItem then
        hooksecurefunc(WeeklyRewardActivityItemMixin, "SetDisplayedItem", function(itemFrame)
            local activity = itemFrame and itemFrame.GetParent and itemFrame:GetParent() or nil
            if NS.RewardRarity and activity then NS.RewardRarity:Invalidate(activity) end
            Skin:RequestRewardRarityRefresh(0.06)
        end)
    end
end

-- ==================== INITIALIZATION AND FRAME LIFECYCLE ====================
function Skin:Initialize()
    if self.initialized or not WeeklyRewardsFrame then
        return
    end
    self.initialized = true

    self:InstallHooks()
    self:HookWeeklyRewardConfirmationPopups()
    WeeklyRewardsFrame:HookScript("OnShow", function()
        Skin.userSelectedActivity = nil
        Skin.pendingSelectedActivity = nil
        Skin.pendingSelectionExpires = nil
        Skin:PrimeOpeningRevealPrivacy()
        Skin:RequestThemeApply("vault open", 0)
        ScheduleOwnedTimer("opening-privacy-pass-1", 0.01, function()
            if WeeklyRewardsFrame and WeeklyRewardsFrame:IsShown() and Skin.pendingOpeningRevealPrivacy then
                Skin:PrimeOpeningRevealPrivacy()
            end
        end)
        ScheduleOwnedTimer("opening-privacy-pass-2", 0.06, function()
            if WeeklyRewardsFrame and WeeklyRewardsFrame:IsShown() and Skin.pendingOpeningRevealPrivacy then
                Skin:PrimeOpeningRevealPrivacy()
            end
        end)
        ScheduleOwnedTimer("open-clear-selection", 0.10, function()
            if WeeklyRewardsFrame and WeeklyRewardsFrame:IsShown() and Skin:IsEnabled() and not Skin.userSelectedActivity then
                SafeCall("clear auto selection on open", Skin.ClearAutomaticSelection, Skin)
            end
        end)
        ScheduleOwnedTimer("open-animation", 0.10, function()
            if WeeklyRewardsFrame and WeeklyRewardsFrame:IsShown() then
                SafeCall("opening animation", Skin.PlayOpeningAnimation, Skin, false)
            end
        end)
    end)

    WeeklyRewardsFrame:HookScript("OnHide", function()
        CancelOwnedTimers("Great Vault closed")
        Skin.applyRequestToken = (Skin.applyRequestToken or 0) + 1
        Skin.rewardRarityToken = (Skin.rewardRarityToken or 0) + 1
        Skin.userSelectedActivity = nil
        Skin.pendingSelectedActivity = nil
        Skin.pendingSelectionExpires = nil
        Skin.pendingOpeningRevealPrivacy = false
        Skin:StopOpeningAnimation(true)
        Skin:StopFrameIdleAnimation()
        Skin:StopBackgroundScene(true, true)
        if NS.RewardRarity then NS.RewardRarity:Reset() end
        if type(collectgarbage) == "function" then collectgarbage("step", 2048) end
    end)

    WeeklyRewardsFrame:HookScript("OnSizeChanged", function()
        ScheduleOwnedTimer("responsive-scale", 0, function()
            if WeeklyRewardsFrame then SafeCall("responsive scale", Skin.ApplyScale, Skin) end
        end)
    end)

    SafeCall("initial skin", self.ApplyTheme, self, false)

    -- Handle frames that were already visible when this module initialized.
    if WeeklyRewardsFrame:IsShown() then
        Skin:PrimeOpeningRevealPrivacy()
        ScheduleOwnedTimer("initial-opening", 0.12, function()
            if WeeklyRewardsFrame and WeeklyRewardsFrame:IsShown() then
                SafeCall("initial opening animation", Skin.PlayOpeningAnimation, Skin, false)
            end
        end)
    end
end
