local addonName, NS = ...

-- ============================================================================
-- VAULTSKINS: OPTIONS INTERFACE
-- ============================================================================

local rawTimerAfter = C_Timer and C_Timer.After
local function ScheduleTask(key, delay, callback)
    if NS.Stability and NS.Stability.Schedule then
        return NS.Stability:Schedule(key, delay, callback)
    end
    if rawTimerAfter then return rawTimerAfter(math.max(0, tonumber(delay) or 0), callback) end
    return callback()
end

-- ========================= OPTIONS STATE AND LABELS =========================
local Options = {}
NS.Options = Options

local L = NS.L or {}
local WHITE_TEXTURE = "Interface\\Buttons\\WHITE8X8"
local BACKDROP_CACHE = {}
local SPEED_ORDER = { "fast", "normal", "cinematic" }
local MODE_ORDER = { "sequential", "rows", "rewardslast", "random", "click", "jackpot" }
local COMPAT_ORDER = { "auto", "native", "minimal" }
local THEME_MODE_ORDER = { "account", "character", "class" }
local PRESET_ORDER = { "off", "fast", "standard", "pulse", "spotlight", "cinematic" }
local SOUND_INTENSITY_ORDER = { "quiet", "normal", "full" }
local SOUND_STYLE_ORDER = { "off", "subtle", "balanced", "cinematic" }
local FRAME_ANIMATION_ORDER = { "off", "subtle", "full" }
local FRAME_PRESET_ORDER = { "off", "subtle", "standard", "cinematic" }
local FRAME_EFFECT_SPEED_ORDER = { "fast", "normal", "cinematic" }
local BACKGROUND_SCENE_STYLE_ORDER = { "off", "subtle", "animated", "cinematic" }
local BACKGROUND_PERFORMANCE_ORDER = { "low", "balanced", "full" }
local LAYOUT_ORDER = { "wide", "classic", "cinematic" }

local SPEED_LABELS = {
    fast = L.SPEED_FAST or "Fast",
    normal = L.SPEED_NORMAL or "Normal",
    cinematic = L.SPEED_CINEMATIC or "Cinematic",
}
local MODE_LABELS = {
    sequential = L.MODE_SEQUENTIAL or "One by one",
    rows = L.MODE_ROWS or "Row by row",
    rewardslast = L.MODE_REWARDS_LAST or "Rewards last",
    random = L.MODE_RANDOM or "Random order",
    click = "Click to reveal",
    jackpot = "Jackpot shuffle",
}
local COMPAT_LABELS = {
    auto = L.COMPAT_AUTO or "Automatic",
    native = L.COMPAT_NATIVE or "VaultSkins",
    minimal = L.COMPAT_MINIMAL or "External UI",
}
local THEME_MODE_LABELS = { account = "All characters", character = "This character", class = "Automatic class" }
local THEME_MODE_SUMMARIES = {
    account = "Uses the same selected theme on every character.",
    character = "Stores a separate selected theme for the current character.",
    class = "Automatically uses the Class theme and follows the current character's class color.",
}
local COMPAT_SUMMARIES = {
    auto = "Recommended. Detects supported UI addons and preserves their button styling when needed.",
    native = "VaultSkins controls the complete Great Vault presentation.",
    minimal = "Keeps more external UI styling while applying only the safest VaultSkins visual layers.",
}
local PRESET_LABELS = { off = "No animation", fast = "Quick Fade", standard = "Soft Glow", pulse = "Border Pulse", spotlight = "Reward Spotlight", cinematic = "Cinematic" }
local REVEAL_STYLE_SUMMARIES = {
    off = "Shows every card immediately with no reveal animation.",
    fast = "A short clean fade only—no flash, no border pulse, and no special burst.",
    standard = "A soft full-card glow with one restrained border pulse.",
    pulse = "A faster card fade followed by a strong two-stage border pulse with no card flash.",
    spotlight = "Keeps locked cards quiet and gives available rewards a brighter, longer spotlight.",
    cinematic = "A slower layered reveal with full glow, border pulses, and a softer echo flash.",
}
local REVEAL_MODE_SUMMARIES = {
    sequential = "Reveals cards from left to right, one at a time.",
    rows = "Reveals each activity row together.",
    rewardslast = "Reveals locked cards first, then saves available rewards for the end.",
    random = "Uses a newly shuffled card order for every reveal.",
    click = "Hides every card until you click it. Each available reward plays its own reveal sound.",
    jackpot = "Runs a casino-style reel shuffle with custom reel ticks, reveals every reward with its own sound, then plays an original max-win fanfare.",
}
local REWARD_EFFECT_ORDER = { "off", "subtle", "full" }
local REWARD_EFFECT_LABELS = { off = "Off", subtle = "Glow only", full = "Full effects" }
local SOUND_INTENSITY_LABELS = { quiet = "Quiet", normal = "Normal", full = "Full" }
local SOUND_STYLE_LABELS = { off = "Off", subtle = "Minimal", balanced = "Balanced", cinematic = "Full", custom = "Custom" }
local SOUND_STYLE_SUMMARIES = {
    off = "All VaultSkins sounds are disabled.",
    subtle = "Only essential opening and reward reveal sounds are enabled.",
    balanced = "Recommended mix of opening, frame, reward, and final sounds.",
    cinematic = "Enables every sound layer with stronger rare-reward emphasis.",
    custom = "Uses the individual sound options selected below.",
}
local FRAME_ANIMATION_LABELS = { off = "Off", subtle = "Subtle", full = "Full" }
local FRAME_PRESET_LABELS = { off = "Steady Glow", subtle = "Breathing Glow", standard = "Flowing Frame", cinematic = "Full Effects", custom = "Flowing Frame" }
local FRAME_EFFECT_SPEED_LABELS = { fast = "Fast", normal = "Normal", cinematic = "Cinematic" }
local BACKGROUND_SCENE_STYLE_LABELS = { off = "Off", subtle = "Subtle", animated = "Animated", cinematic = "Cinematic" }
local BACKGROUND_PERFORMANCE_LABELS = { low = "Low memory", balanced = "Balanced", full = "Full detail" }
local LAYOUT_LABELS = { wide = "Wide", classic = "Classic", cinematic = "Cinematic" }
local LAYOUT_SHORT_LABELS = {
    wide = "More horizontal room",
    classic = "Original-style spacing",
    cinematic = "Larger visual focus",
}

-- ========================= CUSTOM COLOR DEFINITIONS =========================
local CUSTOM_COLOR_FIELDS = {
    { key = "background", label = "Vault background", fallback = "background", tip = "Changes the large background surface behind the entire Great Vault." },
    { key = "panel", label = "Activity panels", fallback = "panel", tip = "Changes the Raids, Dungeons, and World row-panel surfaces." },

    { key = "locked", label = "Locked cards", fallback = "locked", tip = "Changes reward cards that are still locked." },
    { key = "reward", label = "Available rewards", fallback = "reward", tip = "Changes cards that currently contain a reward." },
    { key = "unlocked", label = "Empty unlocked cards", fallback = "unlocked", tip = "Changes unlocked cards that do not currently contain a reward." },
    { key = "cardBorder", label = "Card borders", fallback = "border", tip = "Changes only reward-card borders and card edge lines." },
    { key = "selectionAccent", label = "Selected reward", fallback = "accent", tip = "Changes the outline, check marker, and highlight of the selected reward." },
    { key = "glow", label = "Reward glow", fallback = "glow", tip = "Changes reward-card glow, reveal lighting, and special reward effects." },

    { key = "frameBorder", label = "Outer frame border", fallback = "border", tip = "Changes the static large border around the Great Vault window." },
    { key = "frameAccent", label = "Accent lines", fallback = "accent", tip = "Changes header dividers, row accent bars, and decorative frame lines." },
    { key = "outerGlow", label = "Breathing glow", fallback = "accent", tip = "Changes the animated glowing outline that slowly breathes around the entire Great Vault." },
    { key = "artTint", label = "Artwork tint", fallback = "artTint", tip = "Changes crests, corner ornaments, and themed decorative artwork." },

    { key = "scenePrimary", label = "Scene primary", fallback = "glow", tip = "Changes the main background-scene symbol and particle color." },
    { key = "sceneSecondary", label = "Scene secondary", fallback = "accent", tip = "Changes the secondary background-scene symbol and particle color." },
}

local CUSTOM_COLOR_POSITIONS = {
    { x = 8, y = -94 }, { x = 206, y = -94 },
    { x = 8, y = -158 }, { x = 206, y = -158 },
    { x = 8, y = -196 }, { x = 206, y = -196 },
    { x = 8, y = -234 }, { x = 206, y = -234 },
    { x = 8, y = -298 }, { x = 206, y = -298 },
    { x = 8, y = -336 }, { x = 206, y = -336 },
    { x = 8, y = -400 }, { x = 206, y = -400 },
}

-- =============================== UI FACTORIES ===============================
local function ApplyBackdrop(frame, background, border, edgeSize)
    if not frame or not frame.SetBackdrop then
        return
    end
    edgeSize = tonumber(edgeSize) or 1
    local backdrop = BACKDROP_CACHE[edgeSize]
    if not backdrop then
        backdrop = { bgFile = WHITE_TEXTURE, edgeFile = WHITE_TEXTURE, edgeSize = edgeSize }
        BACKDROP_CACHE[edgeSize] = backdrop
    end
    frame:SetBackdrop(backdrop)
    frame:SetBackdropColor(background.r, background.g, background.b, background.a or 1)
    frame:SetBackdropBorderColor(border.r, border.g, border.b, border.a or 1)
end

local function ColorHex(color)
    local function channel(value)
        value = math.max(0, math.min(1, tonumber(value) or 1))
        return math.floor(value * 255 + 0.5)
    end
    return string.format("%02x%02x%02x", channel(color.r), channel(color.g), channel(color.b))
end

local function SetBrandedTitle(label, theme)
    if not label or not theme then return end
    local accent = theme.accent or { r = 0.72, g = 0.50, b = 1.00 }
    local silver = theme.silver or theme.text or { r = 0.92, g = 0.94, b = 1.00 }
    label:SetText(string.format(
        "|cff%sV|r|cff%sault|r|cff%sSkins|r",
        ColorHex(accent), ColorHex(silver), ColorHex(accent)
    ))
end

-- ========================= THEME AND COLOR HELPERS ==========================
local function GetThemeOrder()
    return NS.GetThemeOrder and NS:GetThemeOrder() or NS.ThemeOrder or { "Midnight" }
end

local function GetThemeLabel(themeID)
    local metadata = NS.ThemeEngine and NS.ThemeEngine:GetThemeMetadata(themeID)
    return metadata and metadata.displayName or themeID
end


-- ============================= CONTROL BUILDERS =============================
local function CreateLabel(parent, text, template)
    local label = parent:CreateFontString(nil, "OVERLAY", template or "GameFontNormal")
    label:SetText(text)
    return label
end

local function CreateCheck(parent, text, y, callback)
    local check = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
    check:SetPoint("TOPLEFT", 14, y)
    check.Text:SetText(text)
    check:SetScript("OnClick", function(self)
        if not Options.refreshing then
            callback(self:GetChecked() and true or false)
        end
    end)
    return check
end


local function CreateSelectRow(parent, text, y, order, labels, callback)
    local label = CreateLabel(parent, text)
    label:SetPoint("TOPLEFT", 18, y)

    local button = CreateFrame("Button", nil, parent, "BackdropTemplate")
    button:SetSize(166, 28)
    button:SetPoint("TOPRIGHT", -18, y + 10)
    button.Text = CreateLabel(button, "", "GameFontHighlight")
    button.Text:SetPoint("LEFT", 12, 0)
    button.Text:SetPoint("RIGHT", -28, 0)
    button.Text:SetJustifyH("LEFT")
    button.Arrow = CreateLabel(button, "▼", "GameFontNormalSmall")
    button.Arrow:SetPoint("RIGHT", -10, 0)

    local menu = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    menu:SetSize(166, #order * 28 + 8)
    menu:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, -2)
    menu:SetFrameLevel(parent:GetFrameLevel() + 120)
    menu:Hide()
    button.Menu = menu
    button.Options = {}

    function button:SetValue(value)
        self.Value = value
        self.Text:SetText(labels[value] or tostring(value or ""))
    end

    for index, value in ipairs(order) do
        local option = CreateFrame("Button", nil, menu, "BackdropTemplate")
        option:SetPoint("TOPLEFT", 4, -4 - (index - 1) * 28)
        option:SetPoint("TOPRIGHT", -4, -4 - (index - 1) * 28)
        option:SetHeight(26)
        option.Value = value
        option.Text = CreateLabel(option, labels[value] or tostring(value), "GameFontHighlightSmall")
        option.Text:SetPoint("LEFT", 12, 0)
        option.Text:SetPoint("RIGHT", -8, 0)
        option.Text:SetJustifyH("LEFT")
        -- A narrow marker avoids UI skins covering dropdown text.
        option.Highlight = option:CreateTexture(nil, "HIGHLIGHT")
        option.Highlight:SetTexture(WHITE_TEXTURE)
        option.Highlight:SetPoint("TOPLEFT", 2, -2)
        option.Highlight:SetPoint("BOTTOMLEFT", 2, 2)
        option.Highlight:SetWidth(3)
        option.Highlight:SetAlpha(0.92)
        option:SetScript("OnClick", function(self)
            menu:Hide()
            if Options.OpenSelectMenu == menu then Options.OpenSelectMenu = nil end
            button:SetValue(self.Value)
            callback(self.Value)
        end)
        button.Options[#button.Options + 1] = option
    end

    button:SetScript("OnClick", function(self)
        if Options.OpenSelectMenu and Options.OpenSelectMenu ~= menu then
            Options.OpenSelectMenu:Hide()
        end
        if menu:IsShown() then
            menu:Hide()
            Options.OpenSelectMenu = nil
        else
            menu:Show()
            Options.OpenSelectMenu = menu
        end
    end)
    button:SetScript("OnHide", function()
        menu:Hide()
        if Options.OpenSelectMenu == menu then Options.OpenSelectMenu = nil end
    end)

    return label, button
end

local function GetRewardEffectMode()
    if not VaultSkinsDB or VaultSkinsDB.rewardRarityEffects == false then return "off" end
    if VaultSkinsDB.rewardSpecialAnimations == false and VaultSkinsDB.rewardJackpotEffects == false then
        return "subtle"
    end
    return "full"
end

local function ApplyBackgroundSceneStyle(style)
    style = style or "animated"
    VaultSkinsDB.backgroundSceneStyle = style
    if style == "off" then
        VaultSkinsDB.backgroundScenes = false
        VaultSkinsDB.backgroundMotion = false
        VaultSkinsDB.backgroundParticles = false
    elseif style == "subtle" then
        VaultSkinsDB.backgroundScenes = true
        VaultSkinsDB.backgroundMotion = false
        VaultSkinsDB.backgroundParticles = false
        VaultSkinsDB.backgroundPerformance = "low"
        VaultSkinsDB.backgroundIntensity = 0.40
    elseif style == "cinematic" then
        VaultSkinsDB.backgroundScenes = true
        VaultSkinsDB.backgroundMotion = true
        VaultSkinsDB.backgroundParticles = true
        VaultSkinsDB.backgroundPerformance = "full"
        VaultSkinsDB.backgroundIntensity = 0.75
    else
        VaultSkinsDB.backgroundSceneStyle = "animated"
        VaultSkinsDB.backgroundScenes = true
        VaultSkinsDB.backgroundMotion = true
        VaultSkinsDB.backgroundParticles = false
        VaultSkinsDB.backgroundPerformance = "balanced"
        VaultSkinsDB.backgroundIntensity = 0.55
    end
end

local function SetRewardEffectMode(mode)
    if mode == "off" then
        VaultSkinsDB.rewardRarityEffects = false
        VaultSkinsDB.rewardItemLevelGlow = false
        VaultSkinsDB.rewardSpecialAnimations = false
        VaultSkinsDB.rewardJackpotEffects = false
    elseif mode == "subtle" then
        VaultSkinsDB.rewardRarityEffects = true
        VaultSkinsDB.rewardItemLevelGlow = true
        VaultSkinsDB.rewardSpecialAnimations = false
        VaultSkinsDB.rewardJackpotEffects = false
    else
        VaultSkinsDB.rewardRarityEffects = true
        VaultSkinsDB.rewardItemLevelGlow = true
        VaultSkinsDB.rewardSpecialAnimations = true
        VaultSkinsDB.rewardJackpotEffects = true
    end
    if NS.Skin then NS.Skin:RequestRewardRarityRefresh(0) end
end

-- ========================== CUSTOM COLOR CONTROLS ===========================
local function CopyColor(color)
    color = type(color) == "table" and color or { r = 1, g = 1, b = 1, a = 1 }
    return { r = color.r or 1, g = color.g or 1, b = color.b or 1, a = color.a or 1 }
end

local function RefreshCustomTheme(reason)
    if NS.ThemeEngine and NS.ThemeEngine.NotifyCustomPaletteChanged then
        NS.ThemeEngine:NotifyCustomPaletteChanged()
    elseif NS.ThemeEngine and NS.ThemeEngine.ClearCache then
        NS.ThemeEngine:ClearCache()
    end
    if NS.Skin then NS.Skin:RequestThemeApply(reason or "custom colors", 0) end
    Options:Refresh()
end

local function GetBaseTheme()
    local themeID = NS.GetActiveThemeID and NS:GetActiveThemeID() or (VaultSkinsDB and VaultSkinsDB.theme) or "Midnight"
    return (NS.GetBaseTheme and NS:GetBaseTheme(themeID)) or NS:GetTheme(themeID)
end

local function PopulateCustomColorsFromBase()
    local baseTheme = GetBaseTheme()
    if not baseTheme then return end
    VaultSkinsDB.customColors = {}
    for _, field in ipairs(CUSTOM_COLOR_FIELDS) do
        VaultSkinsDB.customColors[field.key] = CopyColor(baseTheme[field.fallback] or baseTheme.accent)
    end
end

local function EnsureCustomColors()
    VaultSkinsDB.customColors = type(VaultSkinsDB.customColors) == "table" and VaultSkinsDB.customColors or {}
    local baseTheme = GetBaseTheme()
    for _, field in ipairs(CUSTOM_COLOR_FIELDS) do
        if type(VaultSkinsDB.customColors[field.key]) ~= "table" then
            VaultSkinsDB.customColors[field.key] = CopyColor(baseTheme and (baseTheme[field.fallback] or baseTheme.accent))
        end
    end
end

local function OpenColorPicker(color, callback)
    color = CopyColor(color)
    local previous = CopyColor(color)

    local function applyColor()
        local r, g, b = ColorPickerFrame:GetColorRGB()
        callback({ r = r, g = g, b = b, a = color.a or 1 })
    end

    local function cancelColor()
        callback(previous)
    end

    if ColorPickerFrame.SetupColorPickerAndShow then
        ColorPickerFrame:SetupColorPickerAndShow({
            r = color.r,
            g = color.g,
            b = color.b,
            hasOpacity = false,
            swatchFunc = applyColor,
            cancelFunc = cancelColor,
        })
    else
        ColorPickerFrame.hasOpacity = false
        ColorPickerFrame.previousValues = previous
        ColorPickerFrame.func = applyColor
        ColorPickerFrame.cancelFunc = cancelColor
        ColorPickerFrame:SetColorRGB(color.r, color.g, color.b)
        ColorPickerFrame:Hide()
        ColorPickerFrame:Show()
    end
end

local function CreateColorButton(parent, field, index)
    local button = CreateFrame("Button", nil, parent, "BackdropTemplate")
    button:SetSize(188, 34)
    local position = CUSTOM_COLOR_POSITIONS[index] or { x = 8, y = -94 - (index - 1) * 38 }
    button:SetPoint("TOPLEFT", position.x, position.y)
    button.Field = field

    button.Label = CreateLabel(button, field.label, "GameFontHighlightSmall")
    button.Label:SetPoint("LEFT", 12, 0)
    button.Label:SetPoint("RIGHT", -54, 0)
    button.Label:SetJustifyH("LEFT")

    button.Swatch = button:CreateTexture(nil, "ARTWORK")
    button.Swatch:SetTexture(WHITE_TEXTURE)
    button.Swatch:SetSize(32, 24)
    button.Swatch:SetPoint("RIGHT", -8, 0)

    button:SetScript("OnClick", function(self)
        if not VaultSkinsDB.customThemeEnabled then return end
        EnsureCustomColors()
        OpenColorPicker(VaultSkinsDB.customColors[self.Field.key], function(newColor)
            VaultSkinsDB.customColors[self.Field.key] = CopyColor(newColor)
            RefreshCustomTheme("custom color " .. self.Field.key)
        end)
    end)
    button:SetScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(self.Field.label)
        GameTooltip:AddLine(self.Field.tip, 1, 1, 1, true)
        if not VaultSkinsDB.customThemeEnabled then
            GameTooltip:AddLine("Enable Custom Theme Colors to edit.", 0.55, 0.78, 1, true)
        end
        GameTooltip:Show()
    end)
    button:SetScript("OnLeave", function() GameTooltip:Hide() end)
    return button
end

-- ==================== THEME BUTTONS AND WINDOW POSITION =====================
local function CreateThemeButton(parent, themeID, index)
    local button = CreateFrame("Button", nil, parent, "BackdropTemplate")
    button:SetSize(168, 80)
    local column = (index - 1) % 2
    local row = math.floor((index - 1) / 2)
    button:SetPoint("TOPLEFT", parent, "TOPLEFT", column * 180, -row * 88)

    button.Preview = button:CreateTexture(nil, "BACKGROUND")
    button.Preview:SetTexture(WHITE_TEXTURE)
    button.Preview:SetPoint("TOPLEFT", 3, -3)
    button.Preview:SetPoint("BOTTOMRIGHT", -3, 3)

    button.Header = button:CreateTexture(nil, "BORDER")
    button.Header:SetTexture(WHITE_TEXTURE)
    button.Header:SetPoint("TOPLEFT", 5, -5)
    button.Header:SetPoint("TOPRIGHT", -5, -5)
    button.Header:SetHeight(22)

    button.Swatch = button:CreateTexture(nil, "ARTWORK")
    button.Swatch:SetTexture(WHITE_TEXTURE)
    button.Swatch:SetSize(5, 18)
    button.Swatch:SetPoint("TOPLEFT", 9, -7)

    button.Label = CreateLabel(button, GetThemeLabel(themeID), "GameFontHighlightSmall")
    button.Label:SetPoint("TOPLEFT", button.Swatch, "TOPRIGHT", 7, -1)
    button.Label:SetPoint("RIGHT", button, "RIGHT", -27, 0)
    button.Label:SetJustifyH("LEFT")

    button.Check = button:CreateTexture(nil, "OVERLAY")
    button.Check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
    button.Check:SetSize(18, 18)
    button.Check:SetPoint("TOPRIGHT", -7, -6)

    button.SelectedLine = button:CreateTexture(nil, "OVERLAY")
    button.SelectedLine:SetTexture(WHITE_TEXTURE)
    button.SelectedLine:SetPoint("TOPLEFT", 4, -3)
    button.SelectedLine:SetPoint("TOPRIGHT", -4, -3)
    button.SelectedLine:SetHeight(2)

    button.MiniCards = {}
    for cardIndex = 1, 3 do
        local card = CreateFrame("Frame", nil, button, "BackdropTemplate")
        card:SetSize(43, 34)
        card:SetPoint("BOTTOMLEFT", 8 + ((cardIndex - 1) * 50), 8)
        card:EnableMouse(false)
        button.MiniCards[cardIndex] = card
    end

    button.ThemeName = themeID
    button:SetScript("OnClick", function(self)
        if NS.SetActiveThemeID then NS:SetActiveThemeID(self.ThemeName) else VaultSkinsDB.theme = self.ThemeName end
        Options:Refresh()
        if NS.Skin then NS.Skin:RequestThemeApply("options theme", 0) end
    end)
    button:SetScript("OnEnter", function(self)
        local theme = (NS.GetBaseTheme and NS:GetBaseTheme(self.ThemeName)) or NS:GetTheme(self.ThemeName)
        if theme then self:SetBackdropBorderColor(theme.accent.r, theme.accent.g, theme.accent.b, 1) end
        local metadata = NS.ThemeEngine and NS.ThemeEngine:GetThemeMetadata(self.ThemeName)
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText(metadata and metadata.displayName or self.ThemeName)
        if metadata and metadata.description then GameTooltip:AddLine(metadata.description, 1, 1, 1, true) end
        GameTooltip:AddLine("Click to apply immediately.", 0.53, 0.78, 1)
        if metadata and metadata.pack then GameTooltip:AddLine(metadata.pack, 0.55, 0.62, 0.75) end
        GameTooltip:Show()
    end)
    button:SetScript("OnLeave", function() GameTooltip:Hide(); Options:Refresh() end)
    return button
end

function Options:ResetPosition()
    if VaultSkinsDB then VaultSkinsDB.optionsPosition = nil end
    if self.Frame then
        self.Frame:ClearAllPoints()
        self.Frame:SetPoint("CENTER")
    end
end

function Options:SavePosition()
    local frame = self.Frame
    if not frame or not VaultSkinsDB then return end
    local point, _, relativePoint, x, y = frame:GetPoint(1)
    VaultSkinsDB.optionsPosition = {
        point = point or "CENTER",
        relativePoint = relativePoint or point or "CENTER",
        x = math.floor((x or 0) + 0.5),
        y = math.floor((y or 0) + 0.5),
    }
end

function Options:RestorePosition()
    local frame = self.Frame
    if not frame then return end
    frame:ClearAllPoints()
    local saved = VaultSkinsDB and VaultSkinsDB.optionsPosition
    if type(saved) == "table" and type(saved.point) == "string" then
        frame:SetPoint(saved.point, UIParent, saved.relativePoint or saved.point, tonumber(saved.x) or 0, tonumber(saved.y) or 0)
    else
        frame:SetPoint("CENTER")
    end
    self:ClampToScreen()
end

function Options:ClampToScreen()
    if self.Frame and self.Frame:IsShown() then
        self.Frame:SetClampedToScreen(true)
    end
end

function Options:RebuildThemeButtons()
    local frame = self.Frame
    if not frame or not frame.Pages or not frame.Pages.Themes then return end
    local page = frame.Pages.Themes
    local order = GetThemeOrder()
    local rebuild = #page.ThemeButtons ~= #order
    if not rebuild then
        for index, themeID in ipairs(order) do
            if not page.ThemeButtons[index] or page.ThemeButtons[index].ThemeName ~= themeID then rebuild = true break end
        end
    end
    if not rebuild then return end

    for _, button in ipairs(page.ThemeButtons) do
        button:Hide()
        button:SetParent(nil)
    end
    page.ThemeButtons = {}
    for index, themeID in ipairs(order) do
        page.ThemeButtons[index] = CreateThemeButton(page.ThemeContent, themeID, index)
    end
    page.ThemeContent:SetHeight(math.max(300, math.ceil(#order / 2) * 88))
end

-- ============================= PAGE MANAGEMENT ==============================
local REQUIRED_PAGE_CONTROLS = {
    Themes = { "ThemeScroll", "ThemeContent", "ThemeButtons", "Description" },
    Colors = { "MasterCheck", "BaseText", "ColorButtons", "CopyButton", "ResetButton" },
    Reveal = { "StyleButton", "ModeButton", "FinalCheck", "PreviewButton", "ResetButton", "EffectsButton" },
    Sound = { "StyleButton", "OpeningCheck", "FrameDetailCheck", "RewardCheck", "ImportanceCheck", "FinalCheck", "SyncCheck", "PreviewButton", "JackpotPreviewButton", "ResetButton" },
    Frame = { "StyleButton", "OpeningCheck", "IdleCheck", "ParticlesCheck", "IntensitySlider", "SpeedButton", "PreviewButton", "ResetButton", "Summary" },
    Scene = { "StyleButton", "PerformanceButton", "ParticlesCheck", "IntensitySlider", "PreviewButton", "ResetButton", "Summary" },
    Layout = { "Buttons", "AutoFitCheck", "ScaleSlider", "PreviewButton", "ResetButton" },
    General = { "SkinCheck", "ProfileButton", "CompatButton", "RepairButton", "ResetButton" },
}

function Options:ValidateControls(frame)
    frame = frame or self.Frame
    if not frame or type(frame.Pages) ~= "table" then return false, "page container" end
    for pageName, fields in pairs(REQUIRED_PAGE_CONTROLS) do
        local page = frame.Pages[pageName]
        if not page then return false, pageName .. " page" end
        for _, field in ipairs(fields) do
            if page[field] == nil then return false, pageName .. "." .. field end
        end
    end
    return true
end

function Options:SetPage(pageName)
    local frame = self:Create()
    if self.OpenSelectMenu then
        self.OpenSelectMenu:Hide()
        self.OpenSelectMenu = nil
    end
    if not frame.Pages[pageName] then pageName = "Themes" end
    frame.ActivePage = pageName
    for name, page in pairs(frame.Pages) do page:SetShown(name == pageName) end
    for name, button in pairs(frame.TabButtons) do
        button:SetEnabled(name ~= pageName)
        button:SetAlpha(name == pageName and 1 or 0.78)
    end
    self:Refresh()
end

-- ======================= OPTIONS WINDOW CONSTRUCTION ========================
function Options:Create()
    if self.Frame then return self.Frame end

    local frame = CreateFrame("Frame", "VaultSkinsOptionsFrame", UIParent, "BackdropTemplate")
    self.Frame = frame
    frame:SetSize(460, 620)
    frame:SetFrameStrata("DIALOG")
    frame:SetFrameLevel(2500)
    frame:EnableMouse(true)
    frame:SetMovable(true)
    frame:SetClampedToScreen(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetScript("OnDragStart", frame.StartMoving)
    frame:SetScript("OnDragStop", function(self) self:StopMovingOrSizing(); Options:SavePosition() end)
    frame:Hide()

    frame.Title = CreateLabel(frame, "VaultSkins", "GameFontNormalLarge")
    frame.Title:SetPoint("TOPLEFT", 22, -18)

    frame.CloseButton = CreateFrame("Button", nil, frame, "BackdropTemplate")
    frame.CloseButton:SetSize(25, 25)
    frame.CloseButton:SetPoint("TOPRIGHT", -10, -10)
    frame.CloseButton:SetFrameLevel(frame:GetFrameLevel() + 20)
    frame.CloseButton:EnableMouse(true)
    frame.CloseButton.Text = frame.CloseButton:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    frame.CloseButton.Text:SetPoint("CENTER", 0, 1)
    frame.CloseButton.Text:SetText("×")
    frame.CloseButton:SetScript("OnClick", function() frame:Hide() end)
    frame.CloseButton:SetScript("OnEnter", function(self)
        local themeID = NS.GetActiveThemeID and NS:GetActiveThemeID() or (VaultSkinsDB and VaultSkinsDB.theme)
        local theme = (NS.GetBaseTheme and NS:GetBaseTheme(themeID)) or NS:GetTheme(themeID)
        if theme then
            self:SetBackdropBorderColor(theme.glow.r, theme.glow.g, theme.glow.b, 1)
            self.Text:SetTextColor(1, 1, 1, 1)
        end
    end)
    frame.CloseButton:SetScript("OnLeave", function()
        if Options.Frame and Options.Frame:IsShown() then Options:Refresh() end
    end)

    if UISpecialFrames then
        local alreadyRegistered = false
        for _, frameName in ipairs(UISpecialFrames) do
            if frameName == "VaultSkinsOptionsFrame" then alreadyRegistered = true break end
        end
        if not alreadyRegistered then table.insert(UISpecialFrames, "VaultSkinsOptionsFrame") end
    end

    frame.TabButtons = {}
    local tabs = {
        { id = "Themes", text = L.THEMES or "Themes" },
        { id = "Colors", text = "Colors" },
        { id = "Reveal", text = L.REVEAL or "Reveal" },
        { id = "Sound", text = "Sound" },
        { id = "Frame", text = "Frame" },
        { id = "Scene", text = "Scene" },
        { id = "Layout", text = "Layout" },
        { id = "General", text = L.GENERAL or "General" },
    }
    for index, entry in ipairs(tabs) do
        local tab = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
        tab:SetSize(49, 25)
        tab:SetPoint("TOPLEFT", 14 + (index - 1) * 53, -52)
        tab:SetText(entry.text)
        tab:SetScript("OnClick", function() Options:SetPage(entry.id) end)
        frame.TabButtons[entry.id] = tab
    end

    frame.Pages = {}
    for _, entry in ipairs(tabs) do
        local page = CreateFrame("Frame", nil, frame)
        page:SetPoint("TOPLEFT", 20, -88)
        page:SetPoint("BOTTOMRIGHT", -20, 52)
        page:Hide()
        frame.Pages[entry.id] = page
    end

    local themes = frame.Pages.Themes
    themes.ThemeScroll = CreateFrame("ScrollFrame", nil, themes, "UIPanelScrollFrameTemplate")
    themes.ThemeScroll:SetPoint("TOPLEFT", 2, -4)
    themes.ThemeScroll:SetPoint("BOTTOMRIGHT", -26, 4)
    themes.ThemeContent = CreateFrame("Frame", nil, themes.ThemeScroll)
    themes.ThemeContent:SetSize(360, 270)
    themes.ThemeScroll:SetScrollChild(themes.ThemeContent)
    themes.ThemeButtons = {}
    themes.Description = CreateLabel(themes, "", "GameFontDisableSmall")
    themes.Description:SetPoint("BOTTOMLEFT", 5, 7)
    themes.Description:SetPoint("BOTTOMRIGHT", -5, 7)
    themes.Description:SetJustifyH("LEFT")
    themes.Description:SetHeight(1)
    themes.Description:Hide()

    local reveal = frame.Pages.Reveal

    reveal.OpeningHeader = CreateLabel(reveal, "Opening reveal", "GameFontNormalLarge")
    reveal.OpeningHeader:SetPoint("TOPLEFT", 14, -4)

    reveal.StyleLabel, reveal.StyleButton = CreateSelectRow(reveal, "Reveal style", -48, PRESET_ORDER, PRESET_LABELS, function(value)
        if NS.SetAnimationPreset then NS:SetAnimationPreset(value) end
        Options:Refresh()
        if value == "off" then
            if NS.Skin then NS.Skin:SkipOpeningAnimation() end
        elseif NS.Skin and WeeklyRewardsFrame and WeeklyRewardsFrame:IsShown() then
            NS.Skin:PlayOpeningAnimation(true)
        end
    end)

    reveal.ModeLabel, reveal.ModeButton = CreateSelectRow(reveal, L.REVEAL_ORDER or "Reveal order", -96, MODE_ORDER, MODE_LABELS, function(value)
        VaultSkinsDB.revealMode = value
        Options:Refresh()
    end)

    reveal.FinalCheck = CreateCheck(reveal, L.FINAL_FLOURISH or "Play final flourish", -142, function(value)
        VaultSkinsDB.finalFlourish = value
    end)

    reveal.PreviewButton = CreateFrame("Button", nil, reveal, "UIPanelButtonTemplate")
    reveal.PreviewButton:SetSize(154, 30)
    reveal.PreviewButton:SetPoint("TOPLEFT", 18, -188)
    reveal.PreviewButton:SetText("Preview reveal")
    reveal.PreviewButton:SetScript("OnClick", function()
        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
        if WeeklyRewardsFrame and not WeeklyRewardsFrame:IsShown() then
            if WeeklyRewards_ShowUI then WeeklyRewards_ShowUI() else WeeklyRewardsFrame:Show() end
            ScheduleTask("options-opening-preview", 0.35, function()
                if NS.Skin and VaultSkinsDB.animationPreset ~= "off" then NS.Skin:PlayOpeningAnimation(true) end
            end)
        elseif NS.Skin and WeeklyRewardsFrame and VaultSkinsDB.animationPreset ~= "off" then
            NS.Skin:PlayOpeningAnimation(true)
        end
    end)

    reveal.ResetButton = CreateFrame("Button", nil, reveal, "UIPanelButtonTemplate")
    reveal.ResetButton:SetSize(154, 30)
    reveal.ResetButton:SetPoint("LEFT", reveal.PreviewButton, "RIGHT", 14, 0)
    reveal.ResetButton:SetText("Reset reveal settings")
    reveal.ResetButton:SetScript("OnClick", function()
        if NS.SetAnimationPreset then NS:SetAnimationPreset("standard") end
        VaultSkinsDB.revealMode = "rewardslast"
        VaultSkinsDB.finalFlourish = true
        SetRewardEffectMode("full")
        Options:Refresh()
    end)

    reveal.Divider = reveal:CreateTexture(nil, "ARTWORK")
    reveal.Divider:SetTexture(WHITE_TEXTURE)
    reveal.Divider:SetPoint("TOPLEFT", 14, -242)
    reveal.Divider:SetPoint("TOPRIGHT", -14, -242)
    reveal.Divider:SetHeight(1)
    reveal.Divider:SetAlpha(0.35)

    reveal.RewardHeader = CreateLabel(reveal, "Reward highlights", "GameFontNormalLarge")
    reveal.RewardHeader:SetPoint("TOPLEFT", 14, -266)

    reveal.EffectsLabel, reveal.EffectsButton = CreateSelectRow(reveal, "Reward effects", -312, REWARD_EFFECT_ORDER, REWARD_EFFECT_LABELS, function(value)
        SetRewardEffectMode(value)
        Options:Refresh()
    end)


    local function ApplySoundStyle(style)
        VaultSkinsDB.soundStyle = style
        if style == "off" then
            VaultSkinsDB.sounds = false
            VaultSkinsDB.soundIntensity = "quiet"
            VaultSkinsDB.openingSounds = false
            VaultSkinsDB.frameDetailSounds = false
            VaultSkinsDB.rewardSounds = false
            VaultSkinsDB.finalSounds = false
            VaultSkinsDB.rewardImportanceSounds = false
        elseif style == "subtle" then
            VaultSkinsDB.sounds = true
            VaultSkinsDB.soundIntensity = "quiet"
            VaultSkinsDB.openingSounds = true
            VaultSkinsDB.frameDetailSounds = false
            VaultSkinsDB.rewardSounds = true
            VaultSkinsDB.finalSounds = false
            VaultSkinsDB.rewardImportanceSounds = false
        elseif style == "cinematic" then
            VaultSkinsDB.sounds = true
            VaultSkinsDB.soundIntensity = "full"
            VaultSkinsDB.openingSounds = true
            VaultSkinsDB.frameDetailSounds = true
            VaultSkinsDB.rewardSounds = true
            VaultSkinsDB.finalSounds = true
            VaultSkinsDB.rewardImportanceSounds = true
        else
            VaultSkinsDB.sounds = true
            VaultSkinsDB.soundIntensity = "normal"
            VaultSkinsDB.openingSounds = true
            VaultSkinsDB.frameDetailSounds = true
            VaultSkinsDB.rewardSounds = true
            VaultSkinsDB.finalSounds = true
            VaultSkinsDB.rewardImportanceSounds = true
            VaultSkinsDB.soundStyle = "balanced"
        end
        VaultSkinsDB.frameSounds = VaultSkinsDB.openingSounds or VaultSkinsDB.frameDetailSounds
        VaultSkinsDB.soundSync = true
    end

    local function MarkSoundCustom()
        VaultSkinsDB.sounds = true
        VaultSkinsDB.soundStyle = "custom"
        VaultSkinsDB.frameSounds = VaultSkinsDB.openingSounds or VaultSkinsDB.frameDetailSounds
    end

    local sound = frame.Pages.Sound
    sound.StyleLabel, sound.StyleButton = CreateSelectRow(sound, "Sound level", -4, SOUND_STYLE_ORDER, SOUND_STYLE_LABELS, function(value)
        ApplySoundStyle(value)
        Options:Refresh()
    end)

    sound.AdaptiveLabel = CreateLabel(sound, "Reveal audio", "GameFontNormal")
    sound.AdaptiveLabel:SetPoint("TOPLEFT", 18, -64)
    sound.AdaptiveValue = CreateLabel(sound, "Automatically matches the selected Reveal style", "GameFontHighlightSmall")
    sound.AdaptiveValue:SetPoint("TOPRIGHT", -20, -64)
    sound.AdaptiveValue:SetWidth(275)
    sound.AdaptiveValue:SetJustifyH("RIGHT")

    sound.Divider = sound:CreateTexture(nil, "ARTWORK")
    sound.Divider:SetTexture(WHITE_TEXTURE)
    sound.Divider:SetPoint("TOPLEFT", 14, -98)
    sound.Divider:SetPoint("TOPRIGHT", -14, -98)
    sound.Divider:SetHeight(1)
    sound.Divider:SetAlpha(0.35)

    sound.OpeningCheck = CreateCheck(sound, "Vault opening sound", -116, function(value)
        VaultSkinsDB.openingSounds = value
        MarkSoundCustom()
        Options:Refresh()
    end)
    sound.FrameDetailCheck = CreateCheck(sound, "Crest, border, and corner sounds", -152, function(value)
        VaultSkinsDB.frameDetailSounds = value
        MarkSoundCustom()
        Options:Refresh()
    end)
    sound.RewardCheck = CreateCheck(sound, "Reward reveal sounds", -188, function(value)
        VaultSkinsDB.rewardSounds = value
        MarkSoundCustom()
        Options:Refresh()
    end)
    sound.ImportanceCheck = CreateCheck(sound, "Emphasize tier and rare rewards", -224, function(value)
        VaultSkinsDB.rewardImportanceSounds = value
        MarkSoundCustom()
        Options:Refresh()
    end)
    sound.FinalCheck = CreateCheck(sound, "Final flourish / max-win sound", -260, function(value)
        VaultSkinsDB.finalSounds = value
        MarkSoundCustom()
        Options:Refresh()
    end)
    sound.SyncCheck = CreateCheck(sound, "Synchronize sounds with animations", -296, function(value)
        VaultSkinsDB.soundSync = value
        MarkSoundCustom()
        Options:Refresh()
    end)

    sound.PreviewButton = CreateFrame("Button", nil, sound, "UIPanelButtonTemplate")
    sound.PreviewButton:SetSize(132, 28)
    sound.PreviewButton:SetPoint("TOPLEFT", 18, -340)
    sound.PreviewButton:SetText("Preview reveal")
    sound.PreviewButton:SetScript("OnClick", function()
        if NS.Skin and NS.Skin.PreviewSoundSequence then
            if not NS.Skin:PreviewSoundSequence("reveal") then
                ApplySoundStyle("balanced")
                Options:Refresh()
                NS.Skin:PreviewSoundSequence("reveal")
            end
        end
    end)
    sound.JackpotPreviewButton = CreateFrame("Button", nil, sound, "UIPanelButtonTemplate")
    sound.JackpotPreviewButton:SetSize(132, 28)
    sound.JackpotPreviewButton:SetPoint("LEFT", sound.PreviewButton, "RIGHT", 8, 0)
    sound.JackpotPreviewButton:SetText("Preview jackpot")
    sound.JackpotPreviewButton:SetScript("OnClick", function()
        if NS.Skin and NS.Skin.PreviewSoundSequence then
            if not NS.Skin:PreviewSoundSequence("jackpot") then
                ApplySoundStyle("balanced")
                Options:Refresh()
                NS.Skin:PreviewSoundSequence("jackpot")
            end
        end
    end)
    sound.ResetButton = CreateFrame("Button", nil, sound, "UIPanelButtonTemplate")
    sound.ResetButton:SetSize(106, 28)
    sound.ResetButton:SetPoint("LEFT", sound.JackpotPreviewButton, "RIGHT", 8, 0)
    sound.ResetButton:SetText("Reset sounds")
    sound.ResetButton:SetScript("OnClick", function()
        ApplySoundStyle("balanced")
        Options:Refresh()
    end)

    sound.StyleSummary = CreateLabel(sound, "", "GameFontHighlightSmall")
    sound.StyleSummary:SetPoint("TOPLEFT", 18, -390)
    sound.StyleSummary:SetPoint("RIGHT", -18, 0)
    sound.StyleSummary:SetJustifyH("LEFT")
    sound.StyleSummary:SetHeight(32)

    sound.Help = CreateLabel(sound, "", "GameFontDisableSmall")
    sound.Help:SetPoint("TOPLEFT", 18, -432)
    sound.Help:SetPoint("RIGHT", -18, 0)
    sound.Help:SetHeight(1)
    sound.Help:Hide()

    local framePage = frame.Pages.Frame
    framePage.StyleLabel, framePage.StyleButton = CreateSelectRow(framePage, "Frame effect style", -4, FRAME_PRESET_ORDER, FRAME_PRESET_LABELS, function(value)
        if NS.SetFramePreset then NS:SetFramePreset(value) end
        if NS.Skin then NS.Skin:RefreshFrameAnimation() end
        Options:Refresh()
    end)

    local function RefreshFrameEffects()
        if NS.Skin then NS.Skin:RefreshFrameAnimation() end
        Options:Refresh()
    end

    framePage.OpeningCheck = CreateCheck(framePage, "Play an opening frame flourish", -62, function(value)
        VaultSkinsDB.frameOpeningAnimation = value
        RefreshFrameEffects()
    end)
    framePage.IdleCheck = CreateCheck(framePage, "Keep the breathing glow active", -96, function(value)
        VaultSkinsDB.frameIdleAnimation = value
        RefreshFrameEffects()
    end)
    framePage.ParticlesCheck = CreateCheck(framePage, "Show decorative frame particles", -130, function(value)
        VaultSkinsDB.frameParticles = value
        RefreshFrameEffects()
    end)

    framePage.IntensityLabel = CreateLabel(framePage, "Glow and effect intensity")
    framePage.IntensityLabel:SetPoint("TOPLEFT", 18, -184)
    framePage.IntensityValue = CreateLabel(framePage, "100%", "GameFontHighlight")
    framePage.IntensityValue:SetPoint("TOPRIGHT", -18, -184)
    framePage.IntensitySlider = CreateFrame("Slider", nil, framePage, "OptionsSliderTemplate")
    framePage.IntensitySlider:SetPoint("TOPLEFT", 22, -218)
    framePage.IntensitySlider:SetPoint("TOPRIGHT", -22, -218)
    framePage.IntensitySlider:SetHeight(18)
    framePage.IntensitySlider:SetMinMaxValues(0.40, 1.40)
    framePage.IntensitySlider:SetValueStep(0.05)
    framePage.IntensitySlider:SetObeyStepOnDrag(true)
    framePage.IntensitySlider:SetScript("OnValueChanged", function(_, value)
        value = math.floor(value * 20 + 0.5) / 20
        framePage.IntensityValue:SetFormattedText("%d%%", value * 100)
        if not Options.refreshing then
            VaultSkinsDB.frameIntensity = value
            if NS.Skin then NS.Skin:RefreshFrameAnimation() end
        end
    end)

    framePage.SpeedLabel, framePage.SpeedButton = CreateSelectRow(framePage, "Motion speed", -262, FRAME_EFFECT_SPEED_ORDER, FRAME_EFFECT_SPEED_LABELS, function(value)
        VaultSkinsDB.frameEffectSpeed = value
        RefreshFrameEffects()
    end)

    framePage.Summary = CreateLabel(framePage, "", "GameFontDisableSmall")
    framePage.Summary:SetPoint("TOPLEFT", 18, -320)
    framePage.Summary:SetPoint("RIGHT", -18, 0)
    framePage.Summary:SetJustifyH("LEFT")
    framePage.Summary:SetJustifyV("TOP")
    framePage.Summary:SetHeight(44)

    framePage.PreviewButton = CreateFrame("Button", nil, framePage, "UIPanelButtonTemplate")
    framePage.PreviewButton:SetSize(130, 28)
    framePage.PreviewButton:SetPoint("TOPLEFT", 18, -382)
    framePage.PreviewButton:SetText("Preview frame")
    framePage.PreviewButton:SetScript("OnClick", function()
        if NS.Skin then NS.Skin:PreviewFrameAnimation() end
    end)
    framePage.ResetButton = CreateFrame("Button", nil, framePage, "UIPanelButtonTemplate")
    framePage.ResetButton:SetSize(130, 28)
    framePage.ResetButton:SetPoint("LEFT", framePage.PreviewButton, "RIGHT", 12, 0)
    framePage.ResetButton:SetText("Reset frame")
    framePage.ResetButton:SetScript("OnClick", function()
        if NS.SetFramePreset then NS:SetFramePreset("standard") end
        if NS.Skin then NS.Skin:RefreshFrameAnimation() end
        Options:Refresh()
    end)
    framePage.Help = CreateLabel(framePage, "", "GameFontDisableSmall")
    framePage.Help:SetPoint("TOPLEFT", 18, -424)
    framePage.Help:SetPoint("RIGHT", -18, 0)
    framePage.Help:SetHeight(1)
    framePage.Help:Hide()

    local scene = frame.Pages.Scene
    scene.StyleLabel, scene.StyleButton = CreateSelectRow(scene, "Scene style", -4, BACKGROUND_SCENE_STYLE_ORDER, BACKGROUND_SCENE_STYLE_LABELS, function(value)
        ApplyBackgroundSceneStyle(value)
        if NS.Skin and NS.Skin.RefreshBackgroundScene then NS.Skin:RefreshBackgroundScene() end
        Options:Refresh()
    end)
    scene.PerformanceLabel, scene.PerformanceButton = CreateSelectRow(scene, "Visual quality", -62, BACKGROUND_PERFORMANCE_ORDER, BACKGROUND_PERFORMANCE_LABELS, function(value)
        VaultSkinsDB.backgroundPerformance = value
        if NS.Skin and NS.Skin.RefreshBackgroundScene then NS.Skin:RefreshBackgroundScene() end
        Options:Refresh()
    end)
    scene.ParticlesCheck = CreateCheck(scene, "Show scene particles", -112, function(value)
        VaultSkinsDB.backgroundParticles = value
        if NS.Skin and NS.Skin.RefreshBackgroundScene then NS.Skin:RefreshBackgroundScene() end
        Options:Refresh()
    end)
    scene.IntensityLabel = CreateLabel(scene, "Scene intensity")
    scene.IntensityLabel:SetPoint("TOPLEFT", 18, -166)
    scene.IntensityValue = CreateLabel(scene, "55%", "GameFontHighlight")
    scene.IntensityValue:SetPoint("TOPRIGHT", -18, -166)
    scene.IntensitySlider = CreateFrame("Slider", nil, scene, "OptionsSliderTemplate")
    scene.IntensitySlider:SetPoint("TOPLEFT", 22, -200)
    scene.IntensitySlider:SetPoint("TOPRIGHT", -22, -200)
    scene.IntensitySlider:SetHeight(18)
    scene.IntensitySlider:SetMinMaxValues(0.20, 1.00)
    scene.IntensitySlider:SetValueStep(0.05)
    scene.IntensitySlider:SetObeyStepOnDrag(true)
    scene.IntensitySlider:SetScript("OnValueChanged", function(_, value)
        value = math.floor(value * 20 + 0.5) / 20
        scene.IntensityValue:SetFormattedText("%d%%", value * 100)
        if not Options.refreshing then
            VaultSkinsDB.backgroundIntensity = value
            if NS.Skin and NS.Skin.RefreshBackgroundScene then NS.Skin:RefreshBackgroundScene() end
        end
    end)
    scene.PreviewButton = CreateFrame("Button", nil, scene, "UIPanelButtonTemplate")
    scene.PreviewButton:SetSize(138, 28)
    scene.PreviewButton:SetPoint("TOPLEFT", 18, -236)
    scene.PreviewButton:SetText("Preview scene")
    scene.PreviewButton:SetScript("OnClick", function()
        if NS.Skin and NS.Skin.PreviewBackgroundScene then NS.Skin:PreviewBackgroundScene() end
    end)
    scene.ResetButton = CreateFrame("Button", nil, scene, "UIPanelButtonTemplate")
    scene.ResetButton:SetSize(138, 28)
    scene.ResetButton:SetPoint("LEFT", scene.PreviewButton, "RIGHT", 12, 0)
    scene.ResetButton:SetText("Reset scene")
    scene.ResetButton:SetScript("OnClick", function()
        ApplyBackgroundSceneStyle("animated")
        if NS.Skin and NS.Skin.RefreshBackgroundScene then NS.Skin:RefreshBackgroundScene() end
        Options:Refresh()
    end)
    scene.Summary = CreateLabel(scene, "", "GameFontDisableSmall")
    scene.Summary:SetPoint("TOPLEFT", 18, -286)
    scene.Summary:SetPoint("RIGHT", -18, 0)
    scene.Summary:SetJustifyH("LEFT")
    scene.Summary:SetJustifyV("TOP")
    scene.Summary:SetHeight(1)
    scene.Summary:Hide()
    scene.Help = CreateLabel(scene, "", "GameFontDisableSmall")
    scene.Help:SetPoint("TOPLEFT", 18, -288)
    scene.Help:SetPoint("RIGHT", -18, 0)
    scene.Help:SetJustifyH("LEFT")
    scene.Help:SetJustifyV("TOP")
    scene.Help:SetHeight(1)
    scene.Help:Hide()

    local general = frame.Pages.General

    general.AddonHeader = CreateLabel(general, "Addon behavior", "GameFontNormalLarge")
    general.AddonHeader:SetPoint("TOPLEFT", 14, -4)

    general.SkinCheck = CreateCheck(general, "Enable VaultSkins", -42, function(value)
        VaultSkinsDB.skinEnabled = value
        if NS.Skin then NS.Skin:SetSkinEnabled(value) end
        Options:Refresh()
    end)

    general.ProfileLabel, general.ProfileButton = CreateSelectRow(general, "Theme profile", -88, THEME_MODE_ORDER, THEME_MODE_LABELS, function(value)
        VaultSkinsDB.themeMode = value
        if NS.Skin then NS.Skin:RequestThemeApply("theme profile option", 0) end
        Options:Refresh()
    end)
    general.ProfileSummary = CreateLabel(general, "", "GameFontDisableSmall")
    general.ProfileSummary:SetPoint("TOPLEFT", 18, -126)
    general.ProfileSummary:SetPoint("RIGHT", -18, 0)
    general.ProfileSummary:SetJustifyH("LEFT")
    general.ProfileSummary:SetHeight(32)

    general.CompatHeader = CreateLabel(general, "Compatibility", "GameFontNormalLarge")
    general.CompatHeader:SetPoint("TOPLEFT", 14, -176)

    general.CompatLabel, general.CompatButton = CreateSelectRow(general, L.COMPATIBILITY_MODE or "UI compatibility", -214, COMPAT_ORDER, COMPAT_LABELS, function(value)
        VaultSkinsDB.compatibilityMode = value
        if NS.Compatibility then NS.Compatibility:Refresh() end
        if NS.Skin then NS.Skin:RequestThemeApply("compatibility option", 0) end
        Options:Refresh()
    end)
    general.CompatStatus = CreateLabel(general, "", "GameFontHighlightSmall")
    general.CompatStatus:SetPoint("TOPLEFT", 18, -252)
    general.CompatStatus:SetPoint("RIGHT", -18, 0)
    general.CompatStatus:SetJustifyH("LEFT")
    general.CompatHelp = CreateLabel(general, "", "GameFontDisableSmall")
    general.CompatHelp:SetPoint("TOPLEFT", 18, -278)
    general.CompatHelp:SetPoint("RIGHT", -18, 0)
    general.CompatHelp:SetJustifyH("LEFT")
    general.CompatHelp:SetHeight(42)

    general.Divider = general:CreateTexture(nil, "ARTWORK")
    general.Divider:SetTexture(WHITE_TEXTURE)
    general.Divider:SetPoint("TOPLEFT", 18, -326)
    general.Divider:SetPoint("TOPRIGHT", -18, -326)
    general.Divider:SetHeight(1)

    general.ToolsHeader = CreateLabel(general, "Maintenance", "GameFontNormalLarge")
    general.ToolsHeader:SetPoint("TOPLEFT", 14, -346)

    general.RepairButton = CreateFrame("Button", nil, general, "UIPanelButtonTemplate")
    general.RepairButton:SetSize(174, 30)
    general.RepairButton:SetPoint("TOPLEFT", 18, -382)
    general.RepairButton:SetText("Repair visual state")
    general.RepairButton:SetScript("OnClick", function()
        if NS.Skin and NS.Skin.Repair then NS.Skin:Repair() end
        Options:Refresh()
    end)

    general.ResetButton = CreateFrame("Button", nil, general, "UIPanelButtonTemplate")
    general.ResetButton:SetSize(174, 30)
    general.ResetButton:SetPoint("LEFT", general.RepairButton, "RIGHT", 12, 0)
    general.ResetButton:SetText("Reset all settings")
    general.ResetButton:SetScript("OnClick", function()
        StaticPopup_Show("VAULTSKINS_RESET_ALL_CONFIRM")
    end)

    general.ResetNote = CreateLabel(general, "Reset removes custom colors and restores every VaultSkins option to its recommended default.", "GameFontDisableSmall")
    general.ResetNote:SetPoint("TOPLEFT", 18, -426)
    general.ResetNote:SetPoint("RIGHT", -18, 0)
    general.ResetNote:SetJustifyH("LEFT")
    general.ResetNote:SetJustifyV("TOP")
    general.ResetNote:SetHeight(38)

    if StaticPopupDialogs and not StaticPopupDialogs.VAULTSKINS_RESET_ALL_CONFIRM then
        StaticPopupDialogs.VAULTSKINS_RESET_ALL_CONFIRM = {
            text = "Reset every VaultSkins setting?\n\nThis also removes custom colors and per-character theme choices.",
            button1 = YES,
            button2 = NO,
            OnAccept = function()
                if NS.ResetDatabase then NS:ResetDatabase() end
                Options:ResetPosition()
                if NS.Skin and NS.Skin.Repair then NS.Skin:Repair() end
                Options:Refresh()
            end,
            timeout = 0,
            whileDead = true,
            hideOnEscape = true,
            preferredIndex = 3,
        }
    end

    local colors = frame.Pages.Colors
    colors.MasterCheck = CreateCheck(colors, "Use custom colors", -10, function(value)
        VaultSkinsDB.customThemeEnabled = value
        if value then EnsureCustomColors() end
        RefreshCustomTheme("toggle custom colors")
    end)
    colors.BaseText = CreateLabel(colors, "", "GameFontDisableSmall")
    colors.BaseText:SetPoint("TOPLEFT", 18, -46)
    colors.BaseText:SetPoint("RIGHT", -18, 0)
    colors.BaseText:SetJustifyH("LEFT")

    colors.GroupHeaders = {}
    for index, group in ipairs({
        { text = "Vault surfaces", y = -72 },
        { text = "Reward cards", y = -136 },
        { text = "Frame & artwork", y = -276 },
        { text = "Background scene", y = -378 },
    }) do
        local label = CreateLabel(colors, group.text, "GameFontNormal")
        label:SetPoint("TOPLEFT", 8, group.y)
        colors.GroupHeaders[index] = label
    end

    colors.ColorButtons = {}
    for index, field in ipairs(CUSTOM_COLOR_FIELDS) do
        colors.ColorButtons[index] = CreateColorButton(colors, field, index)
    end

    colors.CopyButton = CreateFrame("Button", nil, colors, "UIPanelButtonTemplate")
    colors.CopyButton:SetSize(168, 28)
    colors.CopyButton:SetPoint("TOPLEFT", 18, -444)
    colors.CopyButton:SetText("Start from theme")
    colors.CopyButton:SetScript("OnClick", function()
        PopulateCustomColorsFromBase()
        VaultSkinsDB.customThemeEnabled = true
        RefreshCustomTheme("copy base theme colors")
    end)

    colors.ResetButton = CreateFrame("Button", nil, colors, "UIPanelButtonTemplate")
    colors.ResetButton:SetSize(168, 28)
    colors.ResetButton:SetPoint("LEFT", colors.CopyButton, "RIGHT", 12, 0)
    colors.ResetButton:SetText("Restore theme colors")
    colors.ResetButton:SetScript("OnClick", function()
        VaultSkinsDB.customThemeEnabled = false
        VaultSkinsDB.customColors = {}
        RefreshCustomTheme("reset custom colors")
    end)


    local layout = frame.Pages.Layout
    layout.Buttons = {}
    for index, style in ipairs(LAYOUT_ORDER) do
        local button = CreateFrame("Button", nil, layout, "BackdropTemplate")
        button:SetSize(126, 96)
        button:SetPoint("TOPLEFT", 7 + (index - 1) * 136, -18)
        button.Style = style

        button.Title = CreateLabel(button, LAYOUT_LABELS[style], "GameFontNormal")
        button.Title:SetPoint("TOPLEFT", 10, -9)
        button.Title:SetPoint("TOPRIGHT", -10, -9)
        button.Title:SetJustifyH("LEFT")

        button.Subtitle = CreateLabel(button, LAYOUT_SHORT_LABELS[style], "GameFontDisableSmall")
        button.Subtitle:SetPoint("TOPLEFT", 10, -28)
        button.Subtitle:SetPoint("TOPRIGHT", -8, -28)
        button.Subtitle:SetJustifyH("LEFT")
        button.Subtitle:SetHeight(22)

        button.PreviewPanel = button:CreateTexture(nil, "BORDER")
        button.PreviewPanel:SetTexture(WHITE_TEXTURE)
        button.PreviewPanel:SetPoint("BOTTOMLEFT", 8, 8)
        button.PreviewPanel:SetPoint("BOTTOMRIGHT", -8, 8)
        button.PreviewPanel:SetHeight(34)

        button.MiniRow = button:CreateTexture(nil, "ARTWORK")
        button.MiniRow:SetTexture(WHITE_TEXTURE)
        button.MiniRow:SetPoint("LEFT", button.PreviewPanel, "LEFT", 5, 0)
        local rowWidth = style == "wide" and 25 or (style == "cinematic" and 30 or 28)
        button.MiniRow:SetSize(rowWidth, style == "cinematic" and 24 or 20)

        button.MiniCards = {}
        for cardIndex = 1, 3 do
            local card = button:CreateTexture(nil, "ARTWORK")
            card:SetTexture(WHITE_TEXTURE)
            local cardWidth = style == "wide" and 19 or (style == "cinematic" and 18 or 17)
            local cardHeight = style == "cinematic" and 25 or 20
            card:SetSize(cardWidth, cardHeight)
            card:SetPoint("LEFT", button.MiniRow, "RIGHT", 5 + (cardIndex - 1) * (cardWidth + 3), 0)
            button.MiniCards[cardIndex] = card
        end

        button:SetScript("OnClick", function(self)
            if NS.Layout then NS.Layout:SetStyle(self.Style) end
            Options:Refresh()
        end)
        button:SetScript("OnEnter", function(self)
            local profile = NS.Layout and NS.Layout:GetProfile(self.Style)
            GameTooltip:SetOwner(self, "ANCHOR_TOP")
            GameTooltip:SetText(LAYOUT_LABELS[self.Style] or self.Style)
            GameTooltip:AddLine(profile and profile.description or "", 1, 1, 1, true)
            GameTooltip:Show()
        end)
        button:SetScript("OnLeave", function() GameTooltip:Hide() end)
        layout.Buttons[#layout.Buttons + 1] = button
    end

    layout.AutoFitCheck = CreateCheck(layout, "Automatically fit the Vault to the screen", -138, function(value)
        VaultSkinsDB.autoFit = value
        if NS.Skin then NS.Skin:ApplyScale() end
    end)

    layout.ScaleLabel = CreateLabel(layout, L.VAULT_SCALE or "Vault scale")
    layout.ScaleLabel:SetPoint("TOPLEFT", 18, -186)
    layout.ScaleValue = CreateLabel(layout, "100%", "GameFontHighlight")
    layout.ScaleValue:SetPoint("TOPRIGHT", -18, -186)
    layout.ScaleSlider = CreateFrame("Slider", nil, layout, "OptionsSliderTemplate")
    layout.ScaleSlider:SetPoint("TOPLEFT", 22, -222)
    layout.ScaleSlider:SetPoint("TOPRIGHT", -22, -222)
    layout.ScaleSlider:SetHeight(18)
    layout.ScaleSlider:SetMinMaxValues(0.75, 1.25)
    layout.ScaleSlider:SetValueStep(0.05)
    layout.ScaleSlider:SetObeyStepOnDrag(true)
    layout.ScaleSlider:SetScript("OnValueChanged", function(_, value)
        value = math.floor(value * 20 + 0.5) / 20
        layout.ScaleValue:SetFormattedText("%d%%", value * 100)
        if not Options.refreshing then
            VaultSkinsDB.scale = value
            if NS.Skin then NS.Skin:ApplyScale() end
        end
    end)

    layout.PreviewButton = CreateFrame("Button", nil, layout, "UIPanelButtonTemplate")
    layout.PreviewButton:SetSize(150, 28)
    layout.PreviewButton:SetPoint("TOPLEFT", 18, -272)
    layout.PreviewButton:SetText("Preview layout")
    layout.PreviewButton:SetScript("OnClick", function()
        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
        if WeeklyRewardsFrame and not WeeklyRewardsFrame:IsShown() then
            if WeeklyRewards_ShowUI then WeeklyRewards_ShowUI() else WeeklyRewardsFrame:Show() end
        end
        ScheduleTask("layout-preview", 0.15, function()
            if NS.Layout and WeeklyRewardsFrame and WeeklyRewardsFrame:IsShown() then
                NS.Layout:ApplyCurrent(WeeklyRewardsFrame)
            end
        end)
    end)

    layout.ResetButton = CreateFrame("Button", nil, layout, "UIPanelButtonTemplate")
    layout.ResetButton:SetSize(150, 28)
    layout.ResetButton:SetPoint("LEFT", layout.PreviewButton, "RIGHT", 14, 0)
    layout.ResetButton:SetText("Reset layout")
    layout.ResetButton:SetScript("OnClick", function()
        VaultSkinsDB.autoFit = true
        VaultSkinsDB.scale = 1
        if NS.Layout then NS.Layout:SetStyle("classic") end
        if NS.Skin then NS.Skin:ApplyScale() end
        Options:Refresh()
    end)


    frame.Footer = CreateLabel(frame, "", "GameFontDisableSmall")
    frame.Footer:SetPoint("BOTTOMLEFT", 22, 19)
    frame.FooterRight = CreateLabel(frame, "", "GameFontDisableSmall")
    frame.FooterRight:SetPoint("BOTTOMRIGHT", -22, 19)

    frame:SetScript("OnShow", function()
        Options:RestorePosition()
        Options:Refresh()
    end)
    frame:SetScript("OnHide", function()
        if Options.OpenSelectMenu then
            Options.OpenSelectMenu:Hide()
            Options.OpenSelectMenu = nil
        end
        if GameTooltip then GameTooltip:Hide() end
    end)

    local controlsReady, missingControl = self:ValidateControls(frame)
    if not controlsReady then
        print("|cffff5555VaultSkins:|r Settings page failed to initialize: " .. tostring(missingControl))
        return frame
    end

    self:RebuildThemeButtons()
    self:SetPage("Themes")
    return frame
end

-- ========================== OPTIONS STATE REFRESH ===========================
-- The options window previews base theme colors; custom colors apply to the Vault.
function Options:Refresh()
    local frame = self:Create()
    local controlsReady, missingControl = self:ValidateControls(frame)
    if not controlsReady then
        if not self.controlWarningShown then
            self.controlWarningShown = true
            print("|cffff5555VaultSkins:|r Settings refresh skipped: missing " .. tostring(missingControl) .. ". Use /vskin repair after updating.")
        end
        return
    end
    self.refreshing = true
    self:RebuildThemeButtons()

    local activeThemeID = NS.GetActiveThemeID and NS:GetActiveThemeID() or VaultSkinsDB.theme
    local activeTheme = (NS.GetBaseTheme and NS:GetBaseTheme(activeThemeID)) or NS:GetTheme(activeThemeID)
    if not activeTheme then self.refreshing = false return end
    ApplyBackdrop(frame, activeTheme.background, activeTheme.border, 2)
    SetBrandedTitle(frame.Title, activeTheme)
    if frame.CloseButton then
        ApplyBackdrop(frame.CloseButton, activeTheme.panel, activeTheme.accent, 1)
        local closeText = activeTheme.silver or activeTheme.text
        frame.CloseButton.Text:SetTextColor(closeText.r, closeText.g, closeText.b, 1)
    end

    local themes = frame.Pages.Themes
    for _, button in ipairs(themes.ThemeButtons) do
        local theme = (NS.GetBaseTheme and NS:GetBaseTheme(button.ThemeName)) or NS:GetTheme(button.ThemeName)
        local selected = activeThemeID == button.ThemeName
        if theme then
            ApplyBackdrop(button, selected and theme.unlocked or theme.card, selected and theme.accent or theme.border, selected and 2 or 1)
            button.Preview:SetColorTexture(theme.background.r, theme.background.g, theme.background.b, 1)
            button.Header:SetColorTexture(theme.panel.r, theme.panel.g, theme.panel.b, 0.96)
            button.Swatch:SetColorTexture(theme.accent.r, theme.accent.g, theme.accent.b, 1)
            button.Label:SetText(GetThemeLabel(button.ThemeName))
            button.Label:SetTextColor(theme.text.r, theme.text.g, theme.text.b, 1)
            button.Check:SetShown(selected)
            button.SelectedLine:SetColorTexture(theme.accent.r, theme.accent.g, theme.accent.b, 1)
            button.SelectedLine:SetShown(selected)
            for cardIndex, mini in ipairs(button.MiniCards) do
                local reward = cardIndex == 3
                ApplyBackdrop(mini, reward and theme.reward or theme.card, reward and theme.accent or theme.border, reward and 2 or 1)
            end
        end
    end
    local metadata = NS.ThemeEngine and NS.ThemeEngine:GetThemeMetadata(activeThemeID)
    themes.Description:SetText("")
    themes.Description:Hide()

    local reveal = frame.Pages.Reveal
    reveal.StyleButton:SetValue(VaultSkinsDB.animationPreset or "standard")
    reveal.ModeButton:SetValue(VaultSkinsDB.revealMode or "rewardslast")
    reveal.EffectsButton:SetValue(GetRewardEffectMode())
    reveal.FinalCheck:SetChecked(VaultSkinsDB.finalFlourish)

    local effectMode = GetRewardEffectMode()
    reveal.Divider:SetColorTexture(activeTheme.border.r, activeTheme.border.g, activeTheme.border.b, 0.65)

    for _, selectButton in ipairs({ reveal.StyleButton, reveal.ModeButton, reveal.EffectsButton, frame.Pages.Sound.StyleButton, frame.Pages.Frame.StyleButton, frame.Pages.Frame.SpeedButton, frame.Pages.Scene.StyleButton, frame.Pages.Scene.PerformanceButton, frame.Pages.General.ProfileButton, frame.Pages.General.CompatButton }) do
        ApplyBackdrop(selectButton, activeTheme.card, activeTheme.border, 1)
        selectButton.Text:SetTextColor(activeTheme.text.r, activeTheme.text.g, activeTheme.text.b, 1)
        selectButton.Arrow:SetTextColor(activeTheme.accent.r, activeTheme.accent.g, activeTheme.accent.b, 1)
        ApplyBackdrop(selectButton.Menu, activeTheme.background, activeTheme.border, 1)
        for _, option in ipairs(selectButton.Options or {}) do
            ApplyBackdrop(option, activeTheme.background, activeTheme.border, 1)
            option.Text:SetTextColor(activeTheme.text.r, activeTheme.text.g, activeTheme.text.b, 1)
            option.Highlight:SetVertexColor(activeTheme.accent.r, activeTheme.accent.g, activeTheme.accent.b, 1)
        end
    end

    local sound = frame.Pages.Sound
    local soundStyle = VaultSkinsDB.soundStyle or (VaultSkinsDB.sounds and "balanced" or "off")
    sound.StyleButton:SetValue(soundStyle)
    sound.OpeningCheck:SetChecked(VaultSkinsDB.openingSounds ~= false)
    sound.FrameDetailCheck:SetChecked(VaultSkinsDB.frameDetailSounds ~= false)
    sound.RewardCheck:SetChecked(VaultSkinsDB.rewardSounds ~= false)
    sound.FinalCheck:SetChecked(VaultSkinsDB.finalSounds ~= false)
    sound.ImportanceCheck:SetChecked(VaultSkinsDB.rewardImportanceSounds ~= false)
    sound.SyncCheck:SetChecked(VaultSkinsDB.soundSync ~= false)
    local soundsEnabled = VaultSkinsDB.sounds ~= false and soundStyle ~= "off"
    for _, control in ipairs({ sound.OpeningCheck, sound.FrameDetailCheck, sound.RewardCheck, sound.FinalCheck, sound.SyncCheck }) do
        control:SetEnabled(soundsEnabled)
    end
    sound.ImportanceCheck:SetEnabled(soundsEnabled and VaultSkinsDB.rewardSounds ~= false)
    sound.PreviewButton:SetEnabled(soundsEnabled)
    sound.JackpotPreviewButton:SetEnabled(soundsEnabled)
    sound.StyleSummary:SetText(SOUND_STYLE_SUMMARIES[soundStyle] or SOUND_STYLE_SUMMARIES.custom)
    sound.StyleSummary:SetTextColor(activeTheme.muted.r, activeTheme.muted.g, activeTheme.muted.b, 1)
    sound.AdaptiveLabel:SetTextColor(activeTheme.text.r, activeTheme.text.g, activeTheme.text.b, 1)
    sound.AdaptiveValue:SetTextColor(activeTheme.accent.r, activeTheme.accent.g, activeTheme.accent.b, 1)
    sound.Divider:SetColorTexture(activeTheme.border.r, activeTheme.border.g, activeTheme.border.b, 0.65)

    local general = frame.Pages.General
    local themeMode = VaultSkinsDB.themeMode or "account"
    local compatibilityMode = VaultSkinsDB.compatibilityMode or "auto"
    general.SkinCheck:SetChecked(VaultSkinsDB.skinEnabled ~= false)
    general.ProfileButton:SetValue(themeMode)
    general.ProfileSummary:SetText(THEME_MODE_SUMMARIES[themeMode] or THEME_MODE_SUMMARIES.account)
    general.ProfileSummary:SetTextColor(activeTheme.muted.r, activeTheme.muted.g, activeTheme.muted.b, 1)
    general.CompatButton:SetValue(compatibilityMode)
    general.CompatStatus:SetText(NS.Compatibility and NS.Compatibility:GetStatusText() or "Blizzard UI detected")
    general.CompatStatus:SetTextColor(activeTheme.text.r, activeTheme.text.g, activeTheme.text.b, 0.88)
    general.CompatHelp:SetText(COMPAT_SUMMARIES[compatibilityMode] or COMPAT_SUMMARIES.auto)
    general.CompatHelp:SetTextColor(activeTheme.muted.r, activeTheme.muted.g, activeTheme.muted.b, 1)
    general.Divider:SetColorTexture(activeTheme.border.r, activeTheme.border.g, activeTheme.border.b, 0.65)
    general.ResetNote:SetTextColor(activeTheme.muted.r, activeTheme.muted.g, activeTheme.muted.b, 1)
    local framePage = frame.Pages.Frame
    local frameStyle = VaultSkinsDB.framePreset or "standard"
    if frameStyle == "custom" then
        frameStyle = VaultSkinsDB.frameAnimationMode == "off" and "off"
            or (VaultSkinsDB.frameAnimationMode == "subtle" and "subtle")
            or (((VaultSkinsDB.frameIntensity or 1) >= 1.15 and VaultSkinsDB.frameCornerEffects and VaultSkinsDB.frameParticles) and "cinematic" or "standard")
    end
    framePage.StyleButton:SetValue(frameStyle)
    framePage.SpeedButton:SetValue(VaultSkinsDB.frameEffectSpeed or "normal")
    framePage.OpeningCheck:SetChecked(VaultSkinsDB.frameOpeningAnimation)
    framePage.IdleCheck:SetChecked(VaultSkinsDB.frameIdleAnimation)
    framePage.ParticlesCheck:SetChecked(VaultSkinsDB.frameParticles)
    framePage.IntensitySlider:SetValue(VaultSkinsDB.frameIntensity or 1)
    framePage.IntensityValue:SetFormattedText("%d%%", (VaultSkinsDB.frameIntensity or 1) * 100)

    local frameMotionEnabled = frameStyle ~= "off" and VaultSkinsDB.frameAnimationMode ~= "off"
    for _, control in ipairs({ framePage.OpeningCheck, framePage.IdleCheck, framePage.ParticlesCheck, framePage.SpeedButton }) do
        control:SetEnabled(frameMotionEnabled)
        control:SetAlpha(frameMotionEnabled and 1 or 0.45)
    end
    framePage.IntensitySlider:SetEnabled(true)
    framePage.IntensitySlider:SetAlpha(1)
    if frameStyle == "off" then
        framePage.Summary:SetText("Steady Glow keeps the themed border illuminated without movement or particles.")
    elseif frameStyle == "subtle" then
        framePage.Summary:SetText("Breathing Glow adds a slow, gentle pulse around the frame with minimal motion.")
    elseif frameStyle == "cinematic" then
        framePage.Summary:SetText("Full Effects enables flowing borders, crest motion, corner highlights, and particles.")
    else
        framePage.Summary:SetText("Flowing Frame animates the border, crest, and corners without adding particles.")
    end
    framePage.Summary:SetTextColor(activeTheme.muted.r, activeTheme.muted.g, activeTheme.muted.b, 1)

    local scene = frame.Pages.Scene
    local sceneStyle = VaultSkinsDB.backgroundSceneStyle or (VaultSkinsDB.backgroundScenes == false and "off" or (VaultSkinsDB.backgroundMotion == false and "subtle" or "animated"))
    scene.StyleButton:SetValue(sceneStyle)
    scene.PerformanceButton:SetValue(VaultSkinsDB.backgroundPerformance or "low")
    scene.ParticlesCheck:SetChecked(VaultSkinsDB.backgroundParticles)
    scene.IntensitySlider:SetValue(VaultSkinsDB.backgroundIntensity or 0.55)
    scene.IntensityValue:SetFormattedText("%d%%", (VaultSkinsDB.backgroundIntensity or 0.55) * 100)
    local sceneEnabled = sceneStyle ~= "off" and VaultSkinsDB.backgroundScenes ~= false
    scene.ParticlesCheck:SetEnabled(sceneEnabled)
    scene.PerformanceButton:SetEnabled(sceneEnabled)
    scene.IntensitySlider:SetEnabled(sceneEnabled)
    scene.ParticlesCheck:SetAlpha(sceneEnabled and 1 or 0.45)
    scene.PerformanceButton:SetAlpha(sceneEnabled and 1 or 0.45)
    scene.IntensitySlider:SetAlpha(sceneEnabled and 1 or 0.45)
    scene.Summary:SetText("")
    scene.Summary:Hide()
    scene.Help:SetText("")
    scene.Help:Hide()

    local colors = frame.Pages.Colors
    colors.MasterCheck:SetChecked(VaultSkinsDB.customThemeEnabled == true)
    local baseThemeID = NS.GetActiveThemeID and NS:GetActiveThemeID() or VaultSkinsDB.theme
    local baseMetadata = NS.ThemeEngine and NS.ThemeEngine:GetThemeMetadata(baseThemeID)
    colors.BaseText:SetText("Based on " .. ((baseMetadata and baseMetadata.displayName) or tostring(baseThemeID)))
    colors.BaseText:SetTextColor(activeTheme.muted.r, activeTheme.muted.g, activeTheme.muted.b, 1)
    local baseTheme = (NS.GetBaseTheme and NS:GetBaseTheme(baseThemeID)) or activeTheme
    for _, button in ipairs(colors.ColorButtons or {}) do
        local field = button.Field
        local stored = VaultSkinsDB.customColors and VaultSkinsDB.customColors[field.key]
        local displayColor = (VaultSkinsDB.customThemeEnabled and stored) or (baseTheme and (baseTheme[field.fallback] or baseTheme.accent)) or activeTheme.accent
        ApplyBackdrop(button, activeTheme.card, VaultSkinsDB.customThemeEnabled and activeTheme.border or activeTheme.muted, 1)
        button.Label:SetTextColor(activeTheme.text.r, activeTheme.text.g, activeTheme.text.b, VaultSkinsDB.customThemeEnabled and 1 or 0.74)
        button.Swatch:SetColorTexture(displayColor.r, displayColor.g, displayColor.b, 1)
        button:SetEnabled(VaultSkinsDB.customThemeEnabled == true)
        button:SetAlpha(VaultSkinsDB.customThemeEnabled and 1 or 0.82)
    end

    local layout = frame.Pages.Layout
    layout.AutoFitCheck:SetChecked(VaultSkinsDB.autoFit ~= false)
    layout.ScaleSlider:SetValue(VaultSkinsDB.scale or 1)
    layout.ScaleValue:SetFormattedText("%d%%", (VaultSkinsDB.scale or 1) * 100)
    for _, button in ipairs(layout.Buttons or {}) do
        local selected = button.Style == VaultSkinsDB.layoutStyle
        ApplyBackdrop(button, selected and activeTheme.unlocked or activeTheme.card, selected and activeTheme.accent or activeTheme.border, selected and 2 or 1)
        button.Title:SetTextColor(activeTheme.text.r, activeTheme.text.g, activeTheme.text.b, 1)
        button.Subtitle:SetTextColor(activeTheme.muted.r, activeTheme.muted.g, activeTheme.muted.b, 1)
        button.PreviewPanel:SetColorTexture(activeTheme.background.r, activeTheme.background.g, activeTheme.background.b, 0.95)
        button.MiniRow:SetColorTexture(activeTheme.panel.r, activeTheme.panel.g, activeTheme.panel.b, 1)
        for cardIndex, card in ipairs(button.MiniCards) do
            local reward = cardIndex == 2
            local color = reward and activeTheme.reward or activeTheme.card
            card:SetColorTexture(color.r, color.g, color.b, 1)
        end
    end

    local diagnostics = NS.ThemeEngine and NS.ThemeEngine:GetDiagnostics()
    frame.Footer:SetText(diagnostics and string.format("%s · %d themes · %d pack%s", L.THEME_ENGINE or "Theme Engine", diagnostics.visible, diagnostics.packs, diagnostics.packs == 1 and "" or "s") or (L.OPEN_WITH or "Open anytime with /vskin"))
    frame.FooterRight:SetText("v" .. tostring(NS.VERSION or "?"))
    self.refreshing = false
end

-- ============================ PUBLIC TOGGLE API =============================
function Options:Toggle()
    local frame = self:Create()
    if frame:IsShown() then frame:Hide() else frame:Show() end
end
