local addonName, ns = ...
local L = ns.L or {}

local ASSET_ROOT = "Interface\\AddOns\\" .. tostring(addonName or "Vaultloom") .. "\\Assets\\"
local SPEC_SWITCHER_SCALE_MIN = 0.70
local SPEC_SWITCHER_SCALE_MAX = 1.45
local SPEC_SWITCHER_SCALE_STEP = 0.05
local SPEC_SWITCHER_POSITION_KEY = "spec_switcher_position"
local SPEC_SWITCHER_SCALE_KEY = "spec_switcher_scale"
local SPEC_SWITCHER_DEFAULT_POSITION = {
    point = "TOP",
    relativePoint = "TOP",
    x = 0,
    y = -220,
}
local SPEC_SWITCHER_BUTTON_SIZE = 36
local SPEC_SWITCHER_BUTTON_GAP = 6
local SPEC_SWITCHER_FRAME_MIN_WIDTH = 0
local SPEC_SWITCHER_FRAME_PADDING_X = 7
local SPEC_SWITCHER_FRAME_PADDING_Y = 7
local SPEC_SWITCHER_FRAME_HEADER_HEIGHT = 0
local SPEC_SWITCHER_FRAME_FOOTER_HEIGHT = 0
local SPEC_SWITCHER_COLORS = {
    frameBackdrop = { 0.01, 0.01, 0.02, 0.92 },
    frameBorder = { 0.40, 0.29, 0.12, 0.78 },
    frameInset = { 0.04, 0.04, 0.05, 0.90 },
    accentGold = { 0.94, 0.78, 0.26, 0.92 },
    accentGoldSoft = { 0.86, 0.70, 0.24, 0.44 },
    accentRed = { 0.74, 0.16, 0.18, 0.88 },
    buttonBackdrop = { 0.05, 0.05, 0.06, 0.94 },
    buttonBorder = { 0.22, 0.22, 0.24, 0.88 },
    inactiveRing = { 0.58, 0.56, 0.52, 0.98 },
    activeRing = { 0.96, 0.80, 0.30, 0.98 },
    pendingRing = { 0.88, 0.24, 0.24, 0.96 },
    hoverRing = { 1.00, 0.92, 0.70, 0.72 },
    menuText = { 0.94, 0.90, 0.84, 0.98 },
    menuTextMuted = { 0.74, 0.74, 0.78, 0.88 },
}

local SPEC_SWITCHER_RING_TEXTURE = ASSET_ROOT .. "dock_ring_clean.tga"
local SPEC_SWITCHER_CLASS_SPEC_COUNT = {
    WARRIOR = 3,
    PALADIN = 3,
    HUNTER = 3,
    ROGUE = 3,
    PRIEST = 3,
    DEATHKNIGHT = 3,
    SHAMAN = 3,
    MAGE = 3,
    WARLOCK = 3,
    MONK = 3,
    DRUID = 4,
    DEMONHUNTER = 2,
    EVOKER = 3,
}

local SpecSwitcherRuntime = {
    enabled = false,
    buttons = {},
    pendingLoadout = nil,
}

local function getSpecSwitcherCurrentIndex()
    if C_SpecializationInfo and type(C_SpecializationInfo.GetSpecialization) == "function" then
        local currentIndex = C_SpecializationInfo.GetSpecialization()
        if type(currentIndex) == "number" and currentIndex > 0 then
            return currentIndex
        end
    end

    if type(GetSpecialization) == "function" then
        local currentIndex = GetSpecialization()
        if type(currentIndex) == "number" and currentIndex > 0 then
            return currentIndex
        end
    end

    return nil
end

local function getSpecSwitcherClassInfo()
    return UnitClass("player")
end

local function getSpecSwitcherCount()
    local _, classTag, classID = getSpecSwitcherClassInfo()

    if C_SpecializationInfo and type(C_SpecializationInfo.GetNumSpecializationsForClassID) == "function" and classID then
        local count = C_SpecializationInfo.GetNumSpecializationsForClassID(classID)
        if type(count) == "number" and count > 0 then
            return count
        end
    end

    if type(GetNumSpecializationsForClassID) == "function" and classID then
        local count = GetNumSpecializationsForClassID(classID)
        if type(count) == "number" and count > 0 then
            return count
        end
    end

    return SPEC_SWITCHER_CLASS_SPEC_COUNT[classTag] or 0
end

local function getSpecSwitcherInfo(specIndex)
    local _, _, classID = getSpecSwitcherClassInfo()

    if type(GetSpecializationInfoForClassID) == "function" and classID then
        local specID, name, description, icon, role = GetSpecializationInfoForClassID(classID, specIndex)
        if specID then
            return specID, name, description, icon, role
        end
    end

    if C_SpecializationInfo and type(C_SpecializationInfo.GetSpecializationInfoForClassID) == "function" and classID then
        local specID, name, description, icon, role = C_SpecializationInfo.GetSpecializationInfoForClassID(classID, specIndex)
        if specID then
            return specID, name, description, icon, role
        end
    end

    return nil, "Spec " .. tostring(specIndex or 0), nil, 136243, ""
end

local function getSpecSwitcherCurrentSpecID()
    local currentIndex = getSpecSwitcherCurrentIndex()
    if not currentIndex then
        return nil
    end

    return select(1, getSpecSwitcherInfo(currentIndex))
end

local function getSpecSwitcherSavedLoadouts(specID)
    local loadouts = {}
    if not specID or not (C_ClassTalents and type(C_ClassTalents.GetConfigIDsBySpecID) == "function") then
        return loadouts
    end

    local configIDs = C_ClassTalents.GetConfigIDsBySpecID(specID)
    if type(configIDs) ~= "table" then
        return loadouts
    end

    for _, configID in ipairs(configIDs) do
        local name = "Loadout " .. tostring(configID)
        if C_Traits and type(C_Traits.GetConfigInfo) == "function" then
            local configInfo = C_Traits.GetConfigInfo(configID)
            if type(configInfo) == "table" and type(configInfo.name) == "string" and configInfo.name ~= "" then
                name = configInfo.name
            end
        end

        loadouts[#loadouts + 1] = {
            id = configID,
            name = name,
        }
    end

    table.sort(loadouts, function(a, b)
        return tostring(a.name or "") < tostring(b.name or "")
    end)

    return loadouts
end

local function getSpecSwitcherSelectedLoadoutID(specID)
    if specID and C_ClassTalents and type(C_ClassTalents.GetLastSelectedSavedConfigID) == "function" then
        return C_ClassTalents.GetLastSelectedSavedConfigID(specID)
    end
    return nil
end

local function getSpecSwitcherActiveConfigInfo()
    if not (C_ClassTalents and type(C_ClassTalents.GetActiveConfigID) == "function" and C_Traits and type(C_Traits.GetConfigInfo) == "function") then
        return nil
    end

    local activeConfigID = C_ClassTalents.GetActiveConfigID()
    if not activeConfigID then
        return nil
    end

    local configInfo = C_Traits.GetConfigInfo(activeConfigID)
    if type(configInfo) ~= "table" then
        return nil
    end

    return configInfo
end

local function getSpecSwitcherActiveLoadoutID(specID)
    if not specID then
        return nil
    end

    local currentSpecID = getSpecSwitcherCurrentSpecID()
    if currentSpecID and currentSpecID == specID then
        local activeConfigInfo = getSpecSwitcherActiveConfigInfo()
        local activeName = activeConfigInfo and activeConfigInfo.name or nil
        if type(activeName) == "string" and activeName ~= "" then
            for _, loadoutInfo in ipairs(getSpecSwitcherSavedLoadouts(specID)) do
                if loadoutInfo.name == activeName then
                    return loadoutInfo.id
                end
            end
        end
    end

    return getSpecSwitcherSelectedLoadoutID(specID)
end

local function getSpecSwitcherActiveLoadoutName(specID)
    if not specID then
        return nil
    end

    local currentSpecID = getSpecSwitcherCurrentSpecID()
    if currentSpecID and currentSpecID == specID then
        local activeConfigInfo = getSpecSwitcherActiveConfigInfo()
        if activeConfigInfo and type(activeConfigInfo.name) == "string" and activeConfigInfo.name ~= "" then
            return activeConfigInfo.name
        end
    end

    local selectedID = getSpecSwitcherSelectedLoadoutID(specID)
    if not selectedID then
        return nil
    end

    for _, loadoutInfo in ipairs(getSpecSwitcherSavedLoadouts(specID)) do
        if loadoutInfo.id == selectedID then
            return loadoutInfo.name
        end
    end

    return nil
end

local function getSpecSwitcherOrientation()
    local orientation = ns.GetFeatureOptionValue and ns:GetFeatureOptionValue("spec_switcher", "orientation") or "horizontal"
    if orientation == "vertical" then
        return "vertical"
    end
    return "horizontal"
end

local function getSpecSwitcherFrameStyle()
    local frameStyle = ns.GetFeatureOptionValue and ns:GetFeatureOptionValue("spec_switcher", "frame_style") or "shown"
    if frameStyle == "hidden" then
        return "hidden"
    end
    return "shown"
end

local function switchSpecSwitcherSpec(specIndex, silent)
    if not specIndex then
        return false
    end

    if InCombatLockdown() then
        if not silent then
            ns:ShowFeatureError(L.FEATURE_SPEC_SWITCHER_ERROR_SPEC_COMBAT or "Vaultloom: cannot switch specialization in combat.")
        end
        return false
    end

    local ok = false
    if C_SpecializationInfo and type(C_SpecializationInfo.SetSpecialization) == "function" then
        ok = pcall(C_SpecializationInfo.SetSpecialization, specIndex)
    elseif type(SetSpecialization) == "function" then
        ok = pcall(SetSpecialization, specIndex)
    end

    return ok
end

local function loadSpecSwitcherConfig(configID, silent)
    if not configID then
        return false
    end

    if InCombatLockdown() then
        if not silent then
            ns:ShowFeatureError(L.FEATURE_SPEC_SWITCHER_ERROR_LOADOUT_COMBAT or "Vaultloom: cannot load talents in combat.")
        end
        return false
    end

    if not (C_ClassTalents and type(C_ClassTalents.LoadConfig) == "function") then
        return false
    end

    return pcall(C_ClassTalents.LoadConfig, configID, true)
end

local function ensureSpecSwitcherFrame()
    if SpecSwitcherRuntime.frame then
        return SpecSwitcherRuntime.frame
    end

    local frame = CreateFrame("Frame", "VaultloomSpecSwitcherBar", UIParent, BACKDROP_TEMPLATE)
    frame:SetSize(SPEC_SWITCHER_BUTTON_SIZE + SPEC_SWITCHER_FRAME_PADDING_X * 2, SPEC_SWITCHER_BUTTON_SIZE + SPEC_SWITCHER_FRAME_PADDING_Y * 2)
    frame:SetFrameStrata("MEDIUM")
    frame:SetFrameLevel(10)
    frame:SetClampedToScreen(true)
    frame:SetMovable(true)
    frame:EnableMouse(true)
    frame:EnableMouseWheel(true)
    frame:RegisterForDrag("LeftButton")
    frame:Hide()
    ns:ApplyFeaturePosition(frame, SPEC_SWITCHER_POSITION_KEY, SPEC_SWITCHER_DEFAULT_POSITION)
    ns:ApplyFeatureScale(frame, SPEC_SWITCHER_SCALE_KEY, SPEC_SWITCHER_SCALE_MIN, SPEC_SWITCHER_SCALE_MAX)

    if frame.SetBackdrop then
        frame:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            tile = false,
            edgeSize = 1,
            insets = { left = 1, right = 1, top = 1, bottom = 1 },
        })
        frame:SetBackdropColor(
            SPEC_SWITCHER_COLORS.frameBackdrop[1],
            SPEC_SWITCHER_COLORS.frameBackdrop[2],
            SPEC_SWITCHER_COLORS.frameBackdrop[3],
            SPEC_SWITCHER_COLORS.frameBackdrop[4]
        )
        frame:SetBackdropBorderColor(
            SPEC_SWITCHER_COLORS.frameBorder[1],
            SPEC_SWITCHER_COLORS.frameBorder[2],
            SPEC_SWITCHER_COLORS.frameBorder[3],
            SPEC_SWITCHER_COLORS.frameBorder[4]
        )
    end

    frame.bg = frame:CreateTexture(nil, "BACKGROUND")
    frame.bg:SetAllPoints()
    frame.bg:SetTexture("Interface\\Buttons\\WHITE8X8")
    frame.bg:SetColorTexture(
        SPEC_SWITCHER_COLORS.frameBackdrop[1],
        SPEC_SWITCHER_COLORS.frameBackdrop[2],
        SPEC_SWITCHER_COLORS.frameBackdrop[3],
        0.96
    )

    frame.inner = frame:CreateTexture(nil, "BACKGROUND", nil, 1)
    frame.inner:SetPoint("TOPLEFT", 1, -1)
    frame.inner:SetPoint("BOTTOMRIGHT", -1, 1)
    frame.inner:SetTexture("Interface\\Buttons\\WHITE8X8")
    frame.inner:SetColorTexture(
        SPEC_SWITCHER_COLORS.frameInset[1],
        SPEC_SWITCHER_COLORS.frameInset[2],
        SPEC_SWITCHER_COLORS.frameInset[3],
        SPEC_SWITCHER_COLORS.frameInset[4]
    )

    frame.topAccent = frame:CreateTexture(nil, "ARTWORK")
    frame.topAccent:SetTexture("Interface\\Buttons\\WHITE8X8")
    frame.topAccent:SetColorTexture(
        SPEC_SWITCHER_COLORS.accentGold[1],
        SPEC_SWITCHER_COLORS.accentGold[2],
        SPEC_SWITCHER_COLORS.accentGold[3],
        0.88
    )

    frame.bottomAccent = frame:CreateTexture(nil, "ARTWORK")
    frame.bottomAccent:SetTexture("Interface\\Buttons\\WHITE8X8")
    frame.bottomAccent:SetColorTexture(
        SPEC_SWITCHER_COLORS.accentGold[1],
        SPEC_SWITCHER_COLORS.accentGold[2],
        SPEC_SWITCHER_COLORS.accentGold[3],
        0.88
    )

    frame.caption = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    frame.caption:Hide()

    frame.specLabel = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
    frame.specLabel:Hide()

    frame.status = frame:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    frame.status:Hide()

    frame:SetScript("OnDragStart", function(selfFrame)
        SpecSwitcherRuntime:HideMenu()
        selfFrame:StartMoving()
    end)
    frame:SetScript("OnDragStop", function(selfFrame)
        selfFrame:StopMovingOrSizing()
        ns:StoreFeaturePosition(selfFrame, SPEC_SWITCHER_POSITION_KEY)
    end)
    frame:SetScript("OnEnter", function(selfFrame)
        GameTooltip:SetOwner(selfFrame, "ANCHOR_TOP")
        GameTooltip:AddLine(L.FEATURE_SPEC_SWITCHER_TITLE or "Spec Switcher Bar", 1, 0.82, 0.22, true)
        GameTooltip:AddLine(L.FEATURE_SPEC_SWITCHER_RUNTIME_TOOLTIP_DRAG or "Left drag: move", 0.84, 0.84, 0.82, true)
        GameTooltip:AddLine(L.FEATURE_SPEC_SWITCHER_RUNTIME_TOOLTIP_WHEEL or "Shift + mouse wheel: size", 0.84, 0.84, 0.82, true)
        GameTooltip:Show()
    end)
    frame:SetScript("OnLeave", function()
        GameTooltip:Hide()
    end)
    frame:SetScript("OnMouseWheel", function(selfFrame, delta)
        if type(IsShiftKeyDown) == "function" and not IsShiftKeyDown() then
            return
        end

        local nextScale = ns:GetFeatureScale(SPEC_SWITCHER_SCALE_KEY, SPEC_SWITCHER_SCALE_MIN, SPEC_SWITCHER_SCALE_MAX)
        if delta > 0 then
            nextScale = nextScale + SPEC_SWITCHER_SCALE_STEP
        elseif delta < 0 then
            nextScale = nextScale - SPEC_SWITCHER_SCALE_STEP
        end

        nextScale = ns:ClampNumber(nextScale, SPEC_SWITCHER_SCALE_MIN, SPEC_SWITCHER_SCALE_MAX)
        selfFrame:SetScale(nextScale)
        ns:StoreFeatureScale(SPEC_SWITCHER_SCALE_KEY, nextScale, SPEC_SWITCHER_SCALE_MIN, SPEC_SWITCHER_SCALE_MAX)
    end)

    SpecSwitcherRuntime.frame = frame
    return frame
end

local function ensureSpecSwitcherButton(index)
    local frame = ensureSpecSwitcherFrame()
    local button = SpecSwitcherRuntime.buttons[index]
    if button then
        return button
    end

    button = CreateFrame("Button", nil, frame, BACKDROP_TEMPLATE)
    button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
    button:RegisterForDrag("LeftButton")

    if button.SetBackdrop then
        button:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            tile = false,
            edgeSize = 1,
            insets = { left = 0, right = 0, top = 0, bottom = 0 },
        })
        button:SetBackdropColor(
            SPEC_SWITCHER_COLORS.buttonBackdrop[1],
            SPEC_SWITCHER_COLORS.buttonBackdrop[2],
            SPEC_SWITCHER_COLORS.buttonBackdrop[3],
            SPEC_SWITCHER_COLORS.buttonBackdrop[4]
        )
        button:SetBackdropBorderColor(
            SPEC_SWITCHER_COLORS.buttonBorder[1],
            SPEC_SWITCHER_COLORS.buttonBorder[2],
            SPEC_SWITCHER_COLORS.buttonBorder[3],
            SPEC_SWITCHER_COLORS.buttonBorder[4]
        )
    end

    button.backplate = button:CreateTexture(nil, "BACKGROUND")
    button.backplate:SetAllPoints()
    button.backplate:SetTexture("Interface\\Buttons\\WHITE8X8")
    button.backplate:SetColorTexture(0.06, 0.06, 0.07, 0.94)

    button.iconShadow = button:CreateTexture(nil, "BACKGROUND", nil, 1)
    button.iconShadow:SetPoint("TOPLEFT", 5, -5)
    button.iconShadow:SetPoint("BOTTOMRIGHT", -5, 5)
    button.iconShadow:SetTexture("Interface\\Buttons\\WHITE8X8")
    button.iconShadow:SetColorTexture(0, 0, 0, 0.56)

    button.activeGlow = button:CreateTexture(nil, "BACKGROUND", nil, 1)
    button.activeGlow:SetPoint("TOPLEFT", -2, 2)
    button.activeGlow:SetPoint("BOTTOMRIGHT", 2, -2)
    button.activeGlow:SetTexture(SPEC_SWITCHER_RING_TEXTURE)
    button.activeGlow:SetBlendMode("ADD")
    button.activeGlow:SetAlpha(0)

    button.icon = button:CreateTexture(nil, "ARTWORK")
    button.icon:SetPoint("TOPLEFT", 4, -4)
    button.icon:SetPoint("BOTTOMRIGHT", -4, 4)
    button.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)

    button.iconMask = button:CreateMaskTexture(nil, "ARTWORK")
    button.iconMask:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    button.iconMask:SetPoint("TOPLEFT", button.icon, "TOPLEFT", 0, 0)
    button.iconMask:SetPoint("BOTTOMRIGHT", button.icon, "BOTTOMRIGHT", 0, 0)
    button.icon:AddMaskTexture(button.iconMask)

    button.iconShadowMask = button:CreateMaskTexture(nil, "ARTWORK")
    button.iconShadowMask:SetTexture("Interface\\CHARACTERFRAME\\TempPortraitAlphaMask", "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
    button.iconShadowMask:SetPoint("TOPLEFT", button.iconShadow, "TOPLEFT", 0, 0)
    button.iconShadowMask:SetPoint("BOTTOMRIGHT", button.iconShadow, "BOTTOMRIGHT", 0, 0)
    button.iconShadow:AddMaskTexture(button.iconShadowMask)

    button.highlight = button:CreateTexture(nil, "HIGHLIGHT")
    button.highlight:SetPoint("TOPLEFT", -1, 1)
    button.highlight:SetPoint("BOTTOMRIGHT", 1, -1)
    button.highlight:SetTexture(SPEC_SWITCHER_RING_TEXTURE)
    button.highlight:SetVertexColor(
        SPEC_SWITCHER_COLORS.hoverRing[1],
        SPEC_SWITCHER_COLORS.hoverRing[2],
        SPEC_SWITCHER_COLORS.hoverRing[3],
        SPEC_SWITCHER_COLORS.hoverRing[4]
    )
    button.highlight:SetAlpha(0)

    button.ring = button:CreateTexture(nil, "OVERLAY")
    button.ring:SetPoint("TOPLEFT", -1, 1)
    button.ring:SetPoint("BOTTOMRIGHT", 1, -1)
    button.ring:SetTexture(SPEC_SWITCHER_RING_TEXTURE)
    button.ring:SetVertexColor(
        SPEC_SWITCHER_COLORS.inactiveRing[1],
        SPEC_SWITCHER_COLORS.inactiveRing[2],
        SPEC_SWITCHER_COLORS.inactiveRing[3],
        SPEC_SWITCHER_COLORS.inactiveRing[4]
    )
    button.ring:SetAlpha(0.94)

    button.stateBar = button:CreateTexture(nil, "OVERLAY")
    button.stateBar:SetPoint("BOTTOMLEFT", 11, 3)
    button.stateBar:SetPoint("BOTTOMRIGHT", -11, 3)
    button.stateBar:SetHeight(1)
    button.stateBar:SetTexture("Interface\\Buttons\\WHITE8X8")
    button.stateBar:SetColorTexture(0.56, 0.58, 0.62, 0.50)

    button:SetScript("OnClick", function(selfButton, mouseButton)
        if mouseButton == "RightButton" then
            SpecSwitcherRuntime:ToggleMenuForButton(selfButton)
            return
        end

        if selfButton.isActiveSpec then
            SpecSwitcherRuntime:CycleOrOpenLoadouts(selfButton)
            return
        end

        SpecSwitcherRuntime:HideMenu()
        SpecSwitcherRuntime.pendingLoadout = nil
        switchSpecSwitcherSpec(selfButton.specIndex, false)
    end)
    button:SetScript("OnDragStart", function()
        SpecSwitcherRuntime:HideMenu()
        frame:StartMoving()
    end)
    button:SetScript("OnDragStop", function()
        frame:StopMovingOrSizing()
        ns:StoreFeaturePosition(frame, SPEC_SWITCHER_POSITION_KEY)
    end)
    button:SetScript("OnEnter", function(selfButton)
        if selfButton.highlight then
            selfButton.highlight:SetAlpha(0.62)
        end
        GameTooltip:SetOwner(selfButton, "ANCHOR_RIGHT")
        GameTooltip:AddLine(selfButton.specName or ("Spec " .. tostring(selfButton.specIndex or 0)), 1, 0.82, 0.22, true)

        local loadoutName = selfButton.currentLoadoutName
        if type(loadoutName) == "string" and loadoutName ~= "" then
            GameTooltip:AddLine(loadoutName, 0.92, 0.76, 0.24, true)
        else
            GameTooltip:AddLine(L.FEATURE_SPEC_SWITCHER_RUNTIME_EMPTY or "No saved loadouts", 0.78, 0.78, 0.82, true)
        end

        if InCombatLockdown() then
            GameTooltip:AddLine(L.FEATURE_SPEC_SWITCHER_RUNTIME_TOOLTIP_COMBAT or "Unavailable in combat", 1.00, 0.30, 0.30, true)
        elseif selfButton.isActiveSpec and (tonumber(selfButton.loadoutCount) or 0) > 0 then
            GameTooltip:AddLine(L.FEATURE_SPEC_SWITCHER_RUNTIME_TOOLTIP_CYCLE or "Left click: cycle saved loadouts", 0.20, 1.00, 0.45, true)
        elseif selfButton.isActiveSpec then
            GameTooltip:AddLine(L.FEATURE_SPEC_SWITCHER_RUNTIME_TOOLTIP_OPEN or "Left click: open saved loadouts", 0.20, 1.00, 0.45, true)
        else
            GameTooltip:AddLine(L.FEATURE_SPEC_SWITCHER_RUNTIME_TOOLTIP_SWITCH or "Left click: switch specialization", 1.00, 1.00, 1.00, true)
        end

        GameTooltip:AddLine(L.FEATURE_SPEC_SWITCHER_RUNTIME_TOOLTIP_MENU or "Right click: open saved loadouts", 0.65, 0.85, 1.00, true)
        GameTooltip:Show()
    end)
    button:SetScript("OnLeave", function(selfButton)
        if selfButton.highlight then
            selfButton.highlight:SetAlpha(0)
        end
        GameTooltip:Hide()
    end)

    SpecSwitcherRuntime.buttons[index] = button
    return button
end

function SpecSwitcherRuntime:HideMenu()
    if self.menu then
        self.menu:Hide()
        self.menu.anchorButton = nil
        self.menu.specID = nil
        self.menu.specIndex = nil
    end
end

function SpecSwitcherRuntime:EnsureMenu()
    if self.menu then
        return self.menu
    end

    local menu = CreateFrame("Frame", "VaultloomSpecSwitcherMenu", UIParent, BACKDROP_TEMPLATE)
    menu:SetFrameStrata("HIGH")
    menu:SetFrameLevel(40)
    menu:SetClampedToScreen(true)
    menu:Hide()

    if menu.SetBackdrop then
        menu:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            tile = false,
            edgeSize = 1,
            insets = { left = 1, right = 1, top = 1, bottom = 1 },
        })
        menu:SetBackdropColor(
            SPEC_SWITCHER_COLORS.frameBackdrop[1],
            SPEC_SWITCHER_COLORS.frameBackdrop[2],
            SPEC_SWITCHER_COLORS.frameBackdrop[3],
            0.97
        )
        menu:SetBackdropBorderColor(
            SPEC_SWITCHER_COLORS.frameBorder[1],
            SPEC_SWITCHER_COLORS.frameBorder[2],
            SPEC_SWITCHER_COLORS.frameBorder[3],
            0.90
        )
    end

    menu.bg = menu:CreateTexture(nil, "BACKGROUND")
    menu.bg:SetAllPoints()
    menu.bg:SetTexture("Interface\\Buttons\\WHITE8X8")
    menu.bg:SetColorTexture(0.03, 0.03, 0.04, 0.96)

    menu.overlay = menu:CreateTexture(nil, "BACKGROUND", nil, 1)
    menu.overlay:SetPoint("TOPLEFT", 1, -1)
    menu.overlay:SetPoint("BOTTOMRIGHT", -1, 1)
    menu.overlay:SetTexture("Interface\\Buttons\\WHITE8X8")
    menu.overlay:SetColorTexture(0.05, 0.05, 0.06, 0.94)

    menu.title = menu:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    menu.title:SetPoint("TOPLEFT", 12, -10)
    menu.title:SetPoint("TOPRIGHT", -12, -10)
    menu.title:SetJustifyH("LEFT")
    menu.title:SetTextColor(
        SPEC_SWITCHER_COLORS.accentGold[1],
        SPEC_SWITCHER_COLORS.accentGold[2],
        SPEC_SWITCHER_COLORS.accentGold[3],
        1
    )

    menu.subtitle = menu:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    menu.subtitle:Hide()

    menu.divider = menu:CreateTexture(nil, "ARTWORK")
    menu.divider:SetTexture("Interface\\Buttons\\WHITE8X8")
    menu.divider:SetPoint("TOPLEFT", menu.title, "BOTTOMLEFT", 0, -6)
    menu.divider:SetPoint("TOPRIGHT", -12, 0)
    menu.divider:SetHeight(1)
    menu.divider:SetColorTexture(
        SPEC_SWITCHER_COLORS.accentGoldSoft[1],
        SPEC_SWITCHER_COLORS.accentGoldSoft[2],
        SPEC_SWITCHER_COLORS.accentGoldSoft[3],
        0.44
    )

    menu.buttons = {}
    self.menu = menu
    return menu
end

function SpecSwitcherRuntime:SelectLoadout(specIndex, specID, configID, loadoutName)
    if not configID then
        return
    end

    if InCombatLockdown() then
        ns:ShowFeatureError(L.FEATURE_SPEC_SWITCHER_ERROR_LOADOUT_COMBAT or "Vaultloom: cannot load talents in combat.")
        return
    end

    self:HideMenu()

    local currentSpecID = getSpecSwitcherCurrentSpecID()
    if currentSpecID and currentSpecID == specID then
        self.pendingLoadout = nil
        loadSpecSwitcherConfig(configID, false)
        return
    end

    self.pendingLoadout = {
        specIndex = specIndex,
        specID = specID,
        configID = configID,
        name = loadoutName,
        attempts = 0,
    }

    if not switchSpecSwitcherSpec(specIndex, false) then
        self.pendingLoadout = nil
    elseif C_Timer and type(C_Timer.After) == "function" then
        C_Timer.After(0.20, function()
            if SpecSwitcherRuntime.enabled == true then
                SpecSwitcherRuntime:TryApplyPendingLoadout()
                SpecSwitcherRuntime:RefreshAll()
            end
        end)
    end
end

function SpecSwitcherRuntime:TryApplyPendingLoadout()
    local pendingLoadout = self.pendingLoadout
    if type(pendingLoadout) ~= "table" or not pendingLoadout.configID then
        return
    end

    if InCombatLockdown() then
        return
    end

    local currentSpecID = getSpecSwitcherCurrentSpecID()
    if currentSpecID ~= pendingLoadout.specID then
        return
    end

    if getSpecSwitcherActiveLoadoutID(pendingLoadout.specID) == pendingLoadout.configID then
        self.pendingLoadout = nil
        return
    end

    pendingLoadout.attempts = (tonumber(pendingLoadout.attempts) or 0) + 1
    if pendingLoadout.attempts > 4 then
        self.pendingLoadout = nil
        return
    end

    if loadSpecSwitcherConfig(pendingLoadout.configID, true) and C_Timer and type(C_Timer.After) == "function" then
        C_Timer.After(0.20, function()
            if SpecSwitcherRuntime.enabled ~= true then
                return
            end

            if getSpecSwitcherActiveLoadoutID(pendingLoadout.specID) == pendingLoadout.configID then
                SpecSwitcherRuntime.pendingLoadout = nil
            end

            SpecSwitcherRuntime:RefreshAll()
        end)
    end
end

function SpecSwitcherRuntime:CycleOrOpenLoadouts(button)
    if not button or not button.specID then
        return
    end

    local loadouts = getSpecSwitcherSavedLoadouts(button.specID)
    if #loadouts == 0 then
        self:ToggleMenuForButton(button)
        return
    end

    if #loadouts == 1 then
        local onlyLoadout = loadouts[1]
        if onlyLoadout.id and getSpecSwitcherActiveLoadoutID(button.specID) ~= onlyLoadout.id then
            loadSpecSwitcherConfig(onlyLoadout.id, false)
        else
            self:ToggleMenuForButton(button)
        end
        return
    end

    local currentLoadoutID = getSpecSwitcherActiveLoadoutID(button.specID)
    local nextLoadout = loadouts[1]
    for index, loadoutInfo in ipairs(loadouts) do
        if loadoutInfo.id == currentLoadoutID then
            nextLoadout = loadouts[(index % #loadouts) + 1]
            break
        end
    end

    if nextLoadout and nextLoadout.id then
        self.pendingLoadout = nil
        loadSpecSwitcherConfig(nextLoadout.id, false)
    end
end

function SpecSwitcherRuntime:RefreshMenu()
    local menu = self:EnsureMenu()
    if not menu:IsShown() or not menu.specID then
        return
    end

    local loadouts = getSpecSwitcherSavedLoadouts(menu.specID)
    local activeLoadoutID = getSpecSwitcherActiveLoadoutID(menu.specID)
    local pendingConfigID = self.pendingLoadout and self.pendingLoadout.specID == menu.specID and self.pendingLoadout.configID or nil
    local width = 190
    local rowHeight = 22
    local padding = 10

    menu.title:SetText(menu.specName or (L.UNKNOWN or "Unknown"))

    if #loadouts == 0 then
        loadouts = {
            {
                id = nil,
                name = L.FEATURE_SPEC_SWITCHER_MENU_EMPTY or "No saved loadouts for this spec yet.",
            },
        }
    end

    for index, loadoutInfo in ipairs(loadouts) do
        local row = menu.buttons[index]
        if not row then
            row = CreateFrame("Button", nil, menu, BACKDROP_TEMPLATE)
            row:SetSize(width, rowHeight)

            if row.SetBackdrop then
                row:SetBackdrop({
                    bgFile = "Interface\\Buttons\\WHITE8X8",
                    edgeFile = "Interface\\Buttons\\WHITE8X8",
                    tile = false,
                    edgeSize = 1,
                    insets = { left = 0, right = 0, top = 0, bottom = 0 },
                })
                row:SetBackdropColor(0.05, 0.05, 0.06, 0.96)
                row:SetBackdropBorderColor(0.18, 0.18, 0.20, 0.90)
            end

            row.bg = row:CreateTexture(nil, "BACKGROUND")
            row.bg:SetAllPoints()
            row.bg:SetTexture("Interface\\Buttons\\WHITE8X8")
            row.bg:SetColorTexture(0.06, 0.06, 0.07, 0.98)

            row.highlight = row:CreateTexture(nil, "HIGHLIGHT")
            row.highlight:SetAllPoints()
            row.highlight:SetTexture("Interface\\Buttons\\WHITE8X8")
            row.highlight:SetColorTexture(1, 1, 1, 0.05)

            row.accent = row:CreateTexture(nil, "ARTWORK")
            row.accent:SetPoint("TOPLEFT", 0, 0)
            row.accent:SetPoint("BOTTOMLEFT", 0, 0)
            row.accent:SetWidth(2)
            row.accent:SetTexture("Interface\\Buttons\\WHITE8X8")
            row.accent:SetColorTexture(
                SPEC_SWITCHER_COLORS.accentGoldSoft[1],
                SPEC_SWITCHER_COLORS.accentGoldSoft[2],
                SPEC_SWITCHER_COLORS.accentGoldSoft[3],
                0.24
            )

            row.label = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            row.label:SetPoint("LEFT", 12, 0)
            row.label:SetPoint("RIGHT", -10, 0)
            row.label:SetJustifyH("LEFT")

            menu.buttons[index] = row
        end

        row:Show()
        row:ClearAllPoints()
        if index == 1 then
            row:SetPoint("TOPLEFT", menu.divider, "BOTTOMLEFT", 0, -8)
        else
            row:SetPoint("TOPLEFT", menu.buttons[index - 1], "BOTTOMLEFT", 0, -4)
        end

        row.specIndex = menu.specIndex
        row.specID = menu.specID
        row.configID = loadoutInfo.id
        row.loadoutName = loadoutInfo.name
        row.label:SetText(loadoutInfo.name or ("Loadout " .. tostring(index)))

        if loadoutInfo.id == nil then
            row:Disable()
            row.label:SetTextColor(
                SPEC_SWITCHER_COLORS.menuTextMuted[1],
                SPEC_SWITCHER_COLORS.menuTextMuted[2],
                SPEC_SWITCHER_COLORS.menuTextMuted[3],
                SPEC_SWITCHER_COLORS.menuTextMuted[4]
            )
            row.accent:SetColorTexture(0.30, 0.30, 0.34, 0.28)
            if row.SetBackdropBorderColor then
                row:SetBackdropBorderColor(0.18, 0.18, 0.20, 0.64)
            end
            row:SetScript("OnClick", nil)
        else
            row:Enable()
            if loadoutInfo.id == pendingConfigID then
                row.label:SetTextColor(1.00, 0.90, 0.88, 1)
                row.accent:SetColorTexture(
                    SPEC_SWITCHER_COLORS.accentRed[1],
                    SPEC_SWITCHER_COLORS.accentRed[2],
                    SPEC_SWITCHER_COLORS.accentRed[3],
                    0.96
                )
                if row.SetBackdropBorderColor then
                    row:SetBackdropBorderColor(
                        SPEC_SWITCHER_COLORS.accentRed[1],
                        SPEC_SWITCHER_COLORS.accentRed[2],
                        SPEC_SWITCHER_COLORS.accentRed[3],
                        0.84
                    )
                end
            elseif loadoutInfo.id == activeLoadoutID then
                row.label:SetTextColor(
                    SPEC_SWITCHER_COLORS.accentGold[1],
                    SPEC_SWITCHER_COLORS.accentGold[2],
                    SPEC_SWITCHER_COLORS.accentGold[3],
                    1
                )
                row.accent:SetColorTexture(
                    SPEC_SWITCHER_COLORS.accentGold[1],
                    SPEC_SWITCHER_COLORS.accentGold[2],
                    SPEC_SWITCHER_COLORS.accentGold[3],
                    0.96
                )
                if row.SetBackdropBorderColor then
                    row:SetBackdropBorderColor(
                        SPEC_SWITCHER_COLORS.accentGold[1],
                        SPEC_SWITCHER_COLORS.accentGold[2],
                        SPEC_SWITCHER_COLORS.accentGold[3],
                        0.82
                    )
                end
            else
                row.label:SetTextColor(
                    SPEC_SWITCHER_COLORS.menuText[1],
                    SPEC_SWITCHER_COLORS.menuText[2],
                    SPEC_SWITCHER_COLORS.menuText[3],
                    SPEC_SWITCHER_COLORS.menuText[4]
                )
                row.accent:SetColorTexture(
                    SPEC_SWITCHER_COLORS.accentGoldSoft[1],
                    SPEC_SWITCHER_COLORS.accentGoldSoft[2],
                    SPEC_SWITCHER_COLORS.accentGoldSoft[3],
                    0.26
                )
                if row.SetBackdropBorderColor then
                    row:SetBackdropBorderColor(0.18, 0.18, 0.20, 0.90)
                end
            end

            row:SetScript("OnClick", function(selfRow)
                SpecSwitcherRuntime:SelectLoadout(selfRow.specIndex, selfRow.specID, selfRow.configID, selfRow.loadoutName)
            end)
        end
    end

    for index = #loadouts + 1, #menu.buttons do
        menu.buttons[index]:Hide()
    end

    menu:SetSize(width + padding * 2, 36 + #loadouts * rowHeight + math.max(0, #loadouts - 1) * 4 + padding)

    if menu.anchorButton and menu.anchorButton:IsShown() then
        menu:ClearAllPoints()
        local openUpward = ns:ShouldOpenMenuUpward(menu, menu.anchorButton)
        if getSpecSwitcherOrientation() == "vertical" then
            if openUpward then
                menu:SetPoint("BOTTOMLEFT", menu.anchorButton, "BOTTOMRIGHT", 8, 0)
            else
                menu:SetPoint("TOPLEFT", menu.anchorButton, "TOPRIGHT", 8, 0)
            end
        else
            if openUpward then
                menu:SetPoint("BOTTOMLEFT", menu.anchorButton, "TOPLEFT", 0, 8)
            else
                menu:SetPoint("TOPLEFT", menu.anchorButton, "BOTTOMLEFT", 0, -8)
            end
        end
    end
end

function SpecSwitcherRuntime:ToggleMenuForButton(button)
    if not button or not button.specID then
        return
    end

    if InCombatLockdown() then
        ns:ShowFeatureError(L.FEATURE_SPEC_SWITCHER_ERROR_LOADOUT_COMBAT or "Vaultloom: cannot load talents in combat.")
        return
    end

    local menu = self:EnsureMenu()
    if menu:IsShown() and menu.anchorButton == button then
        self:HideMenu()
        return
    end

    menu.anchorButton = button
    menu.specIndex = button.specIndex
    menu.specID = button.specID
    menu.specName = button.specName
    menu:Show()
    self:RefreshMenu()
end

function SpecSwitcherRuntime:RefreshButtons()
    local frame = ensureSpecSwitcherFrame()
    local orientation = getSpecSwitcherOrientation()
    local frameStyle = getSpecSwitcherFrameStyle()
    local showFrame = frameStyle ~= "hidden"
    local paddingX = showFrame and SPEC_SWITCHER_FRAME_PADDING_X or 0
    local paddingY = showFrame and SPEC_SWITCHER_FRAME_PADDING_Y or 0
    local headerHeight = showFrame and SPEC_SWITCHER_FRAME_HEADER_HEIGHT or 0
    local footerHeight = showFrame and SPEC_SWITCHER_FRAME_FOOTER_HEIGHT or 0
    local specCount = getSpecSwitcherCount()
    local buttonAreaWidth = orientation == "horizontal"
        and (specCount * SPEC_SWITCHER_BUTTON_SIZE + math.max(0, specCount - 1) * SPEC_SWITCHER_BUTTON_GAP)
        or SPEC_SWITCHER_BUTTON_SIZE
    local buttonAreaHeight = orientation == "horizontal"
        and SPEC_SWITCHER_BUTTON_SIZE
        or (specCount * SPEC_SWITCHER_BUTTON_SIZE + math.max(0, specCount - 1) * SPEC_SWITCHER_BUTTON_GAP)
    local frameWidth = showFrame
        and math.max(SPEC_SWITCHER_FRAME_MIN_WIDTH, buttonAreaWidth + paddingX * 2)
        or buttonAreaWidth
    local frameHeight = headerHeight + buttonAreaHeight + footerHeight + paddingY * 2
    local currentIndex = getSpecSwitcherCurrentIndex()
    local currentSpecID = getSpecSwitcherCurrentSpecID()

    frame:SetSize(frameWidth, frameHeight)
    frame.caption:Hide()
    frame.specLabel:Hide()
    frame.status:Hide()

    if frame.SetBackdrop then
        if showFrame then
            frame:SetBackdropColor(
                SPEC_SWITCHER_COLORS.frameBackdrop[1],
                SPEC_SWITCHER_COLORS.frameBackdrop[2],
                SPEC_SWITCHER_COLORS.frameBackdrop[3],
                SPEC_SWITCHER_COLORS.frameBackdrop[4]
            )
            frame:SetBackdropBorderColor(
                SPEC_SWITCHER_COLORS.frameBorder[1],
                SPEC_SWITCHER_COLORS.frameBorder[2],
                SPEC_SWITCHER_COLORS.frameBorder[3],
                SPEC_SWITCHER_COLORS.frameBorder[4]
            )
        else
            frame:SetBackdropColor(0, 0, 0, 0)
            frame:SetBackdropBorderColor(0, 0, 0, 0)
        end
    end
    if frame.bg then
        frame.bg:SetShown(showFrame)
    end
    if frame.inner then
        frame.inner:SetShown(showFrame)
    end
    frame.topAccent:SetShown(showFrame)
    frame.bottomAccent:SetShown(showFrame)

    frame.topAccent:ClearAllPoints()
    frame.bottomAccent:ClearAllPoints()
    if orientation == "horizontal" then
        frame.topAccent:SetPoint("TOPLEFT", frame, "TOPLEFT", 6, -2)
        frame.topAccent:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -6, -2)
        frame.topAccent:SetHeight(1)
        frame.bottomAccent:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 6, 2)
        frame.bottomAccent:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -6, 2)
        frame.bottomAccent:SetHeight(1)
    else
        frame.topAccent:SetPoint("TOPLEFT", frame, "TOPLEFT", 2, -6)
        frame.topAccent:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 2, 6)
        frame.topAccent:SetWidth(1)
        frame.bottomAccent:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -2, -6)
        frame.bottomAccent:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -2, 6)
        frame.bottomAccent:SetWidth(1)
    end
    frame.topAccent:SetColorTexture(
        SPEC_SWITCHER_COLORS.accentGold[1],
        SPEC_SWITCHER_COLORS.accentGold[2],
        SPEC_SWITCHER_COLORS.accentGold[3],
        0.88
    )
    frame.bottomAccent:SetColorTexture(
        SPEC_SWITCHER_COLORS.accentGold[1],
        SPEC_SWITCHER_COLORS.accentGold[2],
        SPEC_SWITCHER_COLORS.accentGold[3],
        0.88
    )

    for specIndex = 1, specCount do
        local button = ensureSpecSwitcherButton(specIndex)
        local specID, specName, _, specIcon = getSpecSwitcherInfo(specIndex)
        local loadouts = getSpecSwitcherSavedLoadouts(specID)
        local activeLoadoutName = getSpecSwitcherActiveLoadoutName(specID)
        local isActiveSpec = specIndex == currentIndex
        local isPendingSpec = type(self.pendingLoadout) == "table" and self.pendingLoadout.specID == specID

        button:SetSize(SPEC_SWITCHER_BUTTON_SIZE, SPEC_SWITCHER_BUTTON_SIZE)
        button:ClearAllPoints()
        if orientation == "horizontal" then
            local xOffset = paddingX + (specIndex - 1) * (SPEC_SWITCHER_BUTTON_SIZE + SPEC_SWITCHER_BUTTON_GAP)
            button:SetPoint("TOPLEFT", frame, "TOPLEFT", xOffset, -(paddingY + headerHeight))
        else
            local xOffset = math.floor((frameWidth - SPEC_SWITCHER_BUTTON_SIZE) * 0.5 + 0.5)
            local yOffset = paddingY + headerHeight + (specIndex - 1) * (SPEC_SWITCHER_BUTTON_SIZE + SPEC_SWITCHER_BUTTON_GAP)
            button:SetPoint("TOPLEFT", frame, "TOPLEFT", xOffset, -yOffset)
        end

        button.specIndex = specIndex
        button.specID = specID
        button.specName = specName or ("Spec " .. tostring(specIndex))
        button.isActiveSpec = isActiveSpec
        button.loadoutCount = #loadouts
        button.currentLoadoutName = isPendingSpec and self.pendingLoadout.name or activeLoadoutName
        button.icon:SetTexture(specIcon or 136243)
        button.highlight:SetAlpha(0)
        button.activeGlow:SetVertexColor(
            isPendingSpec and SPEC_SWITCHER_COLORS.accentRed[1] or SPEC_SWITCHER_COLORS.accentGold[1],
            isPendingSpec and SPEC_SWITCHER_COLORS.accentRed[2] or SPEC_SWITCHER_COLORS.accentGold[2],
            isPendingSpec and SPEC_SWITCHER_COLORS.accentRed[3] or SPEC_SWITCHER_COLORS.accentGold[3],
            1
        )
        button.activeGlow:SetAlpha(isActiveSpec and 0.26 or (isPendingSpec and 0.18 or 0))

        if button.SetBackdrop then
            if showFrame then
                button:SetBackdropColor(0, 0, 0, 0)
                button:SetBackdropBorderColor(0, 0, 0, 0)
            else
                button:SetBackdropColor(0, 0, 0, 0)
                button:SetBackdropBorderColor(0, 0, 0, 0)
            end
        end
        if button.backplate then
            button.backplate:Hide()
        end

        if isActiveSpec then
            button.ring:SetVertexColor(
                SPEC_SWITCHER_COLORS.activeRing[1],
                SPEC_SWITCHER_COLORS.activeRing[2],
                SPEC_SWITCHER_COLORS.activeRing[3],
                SPEC_SWITCHER_COLORS.activeRing[4]
            )
            button.ring:SetAlpha(1)
        elseif isPendingSpec then
            button.ring:SetVertexColor(
                SPEC_SWITCHER_COLORS.pendingRing[1],
                SPEC_SWITCHER_COLORS.pendingRing[2],
                SPEC_SWITCHER_COLORS.pendingRing[3],
                SPEC_SWITCHER_COLORS.pendingRing[4]
            )
            button.ring:SetAlpha(0.96)
        else
            button.ring:SetVertexColor(
                SPEC_SWITCHER_COLORS.inactiveRing[1],
                SPEC_SWITCHER_COLORS.inactiveRing[2],
                SPEC_SWITCHER_COLORS.inactiveRing[3],
                SPEC_SWITCHER_COLORS.inactiveRing[4]
            )
            button.ring:SetAlpha(0.90)
        end

        if #loadouts > 0 then
            if isActiveSpec then
                button.stateBar:SetColorTexture(
                    SPEC_SWITCHER_COLORS.accentGold[1],
                    SPEC_SWITCHER_COLORS.accentGold[2],
                    SPEC_SWITCHER_COLORS.accentGold[3],
                    0.90
                )
            elseif isPendingSpec then
                button.stateBar:SetColorTexture(
                    SPEC_SWITCHER_COLORS.accentRed[1],
                    SPEC_SWITCHER_COLORS.accentRed[2],
                    SPEC_SWITCHER_COLORS.accentRed[3],
                    0.88
                )
            else
                button.stateBar:SetColorTexture(
                    SPEC_SWITCHER_COLORS.accentGoldSoft[1],
                    SPEC_SWITCHER_COLORS.accentGoldSoft[2],
                    SPEC_SWITCHER_COLORS.accentGoldSoft[3],
                    0.54
                )
            end
            button.stateBar:SetShown(showFrame)
        else
            button.stateBar:Hide()
        end

        if isActiveSpec then
            button:SetAlpha(1)
        else
            button:SetAlpha(InCombatLockdown() and 0.68 or 0.86)
        end

        button:Show()
    end

    for index = specCount + 1, #self.buttons do
        self.buttons[index]:Hide()
    end
end

function SpecSwitcherRuntime:RefreshAll()
    if self.enabled ~= true then
        return
    end

    local frame = ensureSpecSwitcherFrame()
    ns:ApplyFeatureScale(frame, SPEC_SWITCHER_SCALE_KEY, SPEC_SWITCHER_SCALE_MIN, SPEC_SWITCHER_SCALE_MAX)
    self:RefreshButtons()
    frame:Show()

    if self.menu and self.menu:IsShown() then
        self:RefreshMenu()
    end
end

function SpecSwitcherRuntime:SetEnabled(enabled)
    enabled = enabled == true
    self.enabled = enabled

    if not self.eventFrame then
        self.eventFrame = CreateFrame("Frame")
        self.eventFrame:SetScript("OnEvent", function(_, event, ...)
            local unit = ...
            if event == "PLAYER_SPECIALIZATION_CHANGED" and unit ~= "player" then
                return
            end

            if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_SPECIALIZATION_CHANGED" then
                if C_Timer and type(C_Timer.After) == "function" then
                    C_Timer.After(0.12, function()
                        if SpecSwitcherRuntime.enabled ~= true then
                            return
                        end
                        SpecSwitcherRuntime:TryApplyPendingLoadout()
                        SpecSwitcherRuntime:RefreshAll()
                    end)
                else
                    SpecSwitcherRuntime:TryApplyPendingLoadout()
                    SpecSwitcherRuntime:RefreshAll()
                end
                return
            end

            SpecSwitcherRuntime:TryApplyPendingLoadout()
            SpecSwitcherRuntime:RefreshAll()
        end)
    end

    self.eventFrame:UnregisterAllEvents()
    self.pendingLoadout = nil

    if not enabled then
        self:HideMenu()
        if self.HideConfigPanel then
            self:HideConfigPanel()
        end
        if self.frame then
            if self.frame.menuButton then
                self.frame.menuButton:Hide()
            end
            self.frame:Hide()
        end
        return
    end

    local frame = ensureSpecSwitcherFrame()
    ns:ApplyFeaturePosition(frame, SPEC_SWITCHER_POSITION_KEY, SPEC_SWITCHER_DEFAULT_POSITION)
    ns:ApplyFeatureScale(frame, SPEC_SWITCHER_SCALE_KEY, SPEC_SWITCHER_SCALE_MIN, SPEC_SWITCHER_SCALE_MAX)
    self.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    self.eventFrame:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
    self.eventFrame:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
    self.eventFrame:RegisterEvent("TRAIT_CONFIG_UPDATED")
    self.eventFrame:RegisterEvent("TRAIT_CONFIG_LIST_UPDATED")
    self.eventFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
    self.eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
    self:RefreshAll()
end

ns:RegisterFeatureDefinition("spec_switcher", {
    id = "spec_switcher",
    order = 20,
    category = "quick_tools",
    title = function() return L.FEATURE_SPEC_SWITCHER_TITLE or "Spec Switcher Bar" end,
    description = function() return L.FEATURE_SPEC_SWITCHER_DESCRIPTION or "Shows a movable bar for specialization and saved talent switches." end,
    note = function() return L.FEATURE_SPEC_SWITCHER_NOTE or "Left click another spec to switch. Left click the active spec to cycle saved loadouts. Right click opens the saved loadout menu." end,
    previewLabel = function() return L.FEATURE_SPEC_SWITCHER_PREVIEW_LABEL or "Floating Bar" end,
    previewNote = function() return L.FEATURE_SPEC_SWITCHER_PREVIEW_NOTE or "Premium spec icons with a compact talent menu, drag support, and Shift + mouse wheel scaling." end,
    optionToggle = {
        key = "orientation",
        label = function() return L.FEATURE_SPEC_SWITCHER_ORIENTATION_LABEL or "Layout" end,
        values = { "horizontal", "vertical" },
        defaultValue = "horizontal",
        valueLabels = {
            horizontal = function() return L.FEATURE_SPEC_SWITCHER_ORIENTATION_HORIZONTAL or "Horizontal" end,
            vertical = function() return L.FEATURE_SPEC_SWITCHER_ORIENTATION_VERTICAL or "Vertical" end,
        },
    },
    secondaryOptionToggle = {
        key = "frame_style",
        label = function() return L.FEATURE_SPEC_SWITCHER_FRAME_LABEL or "Frame" end,
        values = { "shown", "hidden" },
        defaultValue = "shown",
        valueLabels = {
            shown = function() return L.FEATURE_SPEC_SWITCHER_FRAME_SHOWN or "Frame" end,
            hidden = function() return L.FEATURE_SPEC_SWITCHER_FRAME_HIDDEN or "Clean" end,
        },
    },
    requiresReload = false,
    apply = function(isEnabled)
        SpecSwitcherRuntime:SetEnabled(isEnabled == true)
    end,
    onOptionChanged = function()
        if SpecSwitcherRuntime.enabled == true then
            SpecSwitcherRuntime:RefreshAll()
        end
    end,
})
