local _, addonTable = ...
local WipedQoL = addonTable.WipedQoL
local TRANSLATE = addonTable.TRANSLATE or function(msg) return msg end

local TEST_ITEM_ID = 19019
local TEST_ACHIEVEMENT_ID = 9483
local STACK_OFFSET_Y = 3
local ANCHOR_OFFSET_X = 4
local ANCHOR_OFFSET_Y = 2
local LOOT_TOAST_MOVER_OFFSET_Y = -10
local CENTER_SNAP_THRESHOLD = 80
local TEST_MODE_INTERVAL = 5
local TEST_ALERT_KINDS = { "rare", "upgrade" }
local lootMoverFrameLevel = 100

local ANCHORS = {
    loot = {
        key = "lootToasts",
        name = "WipedQoLLootToastAnchor",
        label = "Loot popups",
        width = 260,
        height = 96,
        point = "BOTTOM",
        relativePoint = "BOTTOM",
        x = 0,
        y = 260,
    },
    roll = {
        key = "lootRolls",
        name = "WipedQoLLootRollAnchor",
        label = "Loot roll frames",
        width = 277,
        height = 96,
        point = "CENTER",
        relativePoint = "CENTER",
        x = 0,
        y = 0,
    },
    bonus = {
        key = "bonusRolls",
        name = "WipedQoLBonusRollAnchor",
        label = "Bonus roll frame",
        width = 277,
        height = 96,
        point = "CENTER",
        relativePoint = "CENTER",
        x = 310,
        y = 0,
    },
    achievement = {
        key = "achievementAlerts",
        name = "WipedQoLAchievementAlertAnchor",
        label = "Achievement popups",
        width = 336,
        height = 96,
        point = "CENTER",
        relativePoint = "CENTER",
        x = 0,
        y = 180,
    },
    completion = {
        key = "dungeonCompletionAlerts",
        name = "WipedQoLDungeonCompletionAlertAnchor",
        label = "Dungeon completion popups",
        width = 336,
        height = 96,
        point = "CENTER",
        relativePoint = "CENTER",
        x = 0,
        y = 280,
    },
    challenge = {
        key = "challengeModeCompletion",
        name = "WipedQoLChallengeModeCompletionAnchor",
        label = "Mythic+ completion banner",
        width = 420,
        height = 356,
        point = "TOP",
        relativePoint = "TOP",
        x = 0,
        y = -120,
    },
    activity = {
        key = "monthlyActivityAlerts",
        name = "WipedQoLMonthlyActivityAlertAnchor",
        label = "Journal activity popups",
        width = 256,
        height = 84,
        point = "CENTER",
        relativePoint = "CENTER",
        x = 0,
        y = 380,
    },
}

local LOOT_TOAST_SYSTEM_NAMES = {
    LootAlertSystem = true,
    LootUpgradeAlertSystem = true,
    LootWonAlertSystem = true,
    MoneyWonAlertSystem = true,
    NewCosmeticAlertFrameSystem = true,
}
local ACHIEVEMENT_ALERT_SYSTEM_NAMES = {
    AchievementAlertSystem = true,
    CriteriaAlertSystem = true,
}
local COMPLETION_ALERT_SYSTEM_NAMES = {
    DungeonCompletionAlertSystem = true,
    ScenarioAlertSystem = true,
    InvasionAlertSystem = true,
}
local ACTIVITY_ALERT_SYSTEM_NAMES = {
    MonthlyActivityAlertSystem = true,
    InitiativeTaskCompleteAlertFrameSystem = true,
    WorldQuestCompleteAlertSystem = true,
}
local lootToastOrderState = {
    frames = setmetatable({}, { __mode = "k" }),
    nextOrder = 0,
}
local achievementAlertOrderState = {
    frames = setmetatable({}, { __mode = "k" }),
    nextOrder = 0,
}
local completionAlertOrderState = {
    frames = setmetatable({}, { __mode = "k" }),
    nextOrder = 0,
}
local activityAlertOrderState = {
    frames = setmetatable({}, { __mode = "k" }),
    nextOrder = 0,
}

local MANAGED_POSITION_KEYS = {
    "GroupLootContainer",
    "BonusRollFrame",
}
local originalManagedPositions = {}

local function CanUseFrame(frame)
    if not frame then
        return false
    end
    if frame.IsForbidden and frame:IsForbidden() then
        return false
    end
    return true
end

local function GetPlayerSpecID()
    if GetSpecialization and GetSpecializationInfo then
        local specIndex = GetSpecialization()
        if specIndex then
            return GetSpecializationInfo(specIndex) or 0
        end
    end
    return 0
end

local function GetPlayerClassID()
    if UnitClass then
        return select(3, UnitClass("player")) or 0
    end
    return 0
end

local function BuildTestItemLink(color, name)
    return string.format("|cff%s|Hitem:%d::::::::80:::::::::|h[%s]|h|r", color, TEST_ITEM_ID, TRANSLATE(name))
end

local function IsNamedAlertSystem(system, systemNames)
    if not system then
        return false
    end

    for systemName in pairs(systemNames) do
        if system == _G[systemName] then
            return true
        end
    end

    return false
end

local function IsLootToastSystem(system)
    return IsNamedAlertSystem(system, LOOT_TOAST_SYSTEM_NAMES)
end

local function IsAchievementAlertSystem(system)
    return IsNamedAlertSystem(system, ACHIEVEMENT_ALERT_SYSTEM_NAMES)
end

local function IsCompletionAlertSystem(system)
    return IsNamedAlertSystem(system, COMPLETION_ALERT_SYSTEM_NAMES)
end

local function IsActivityAlertSystem(system)
    return IsNamedAlertSystem(system, ACTIVITY_ALERT_SYSTEM_NAMES)
end

local function GetAnchorConfig(anchorKey)
    return ANCHORS[anchorKey] or ANCHORS.loot
end

local function ClampScale(value)
    value = tonumber(value) or 1
    if value < 0.5 then
        return 0.5
    end
    if value > 1 then
        return 1
    end
    return value
end

local function SetFrameScale(frame, scale)
    if frame and frame.SetScale then
        frame:SetScale(scale)
    end
end

local function IsCombatLocked()
    return InCombatLockdown and InCombatLockdown() == true
end

local function BringLootMoverToFront(anchor)
    if not anchor then
        return
    end

    lootMoverFrameLevel = lootMoverFrameLevel + 10
    anchor:SetFrameLevel(lootMoverFrameLevel)
    if anchor.scaleSlider then
        anchor.scaleSlider:SetFrameLevel(lootMoverFrameLevel + 2)
    end
end

local function AddLootMoverBorder(anchor)
    if not anchor or anchor._wqolWhiteBorder then
        return
    end

    local border = {}
    for _, edge in ipairs({ "top", "bottom", "left", "right" }) do
        border[edge] = anchor:CreateTexture(nil, "OVERLAY", nil, 7)
        border[edge]:SetColorTexture(1, 1, 1, 1)
    end
    border.top:SetPoint("TOPLEFT")
    border.top:SetPoint("TOPRIGHT")
    border.top:SetHeight(1)
    border.bottom:SetPoint("BOTTOMLEFT")
    border.bottom:SetPoint("BOTTOMRIGHT")
    border.bottom:SetHeight(1)
    border.left:SetPoint("TOPLEFT")
    border.left:SetPoint("BOTTOMLEFT")
    border.left:SetWidth(1)
    border.right:SetPoint("TOPRIGHT")
    border.right:SetPoint("BOTTOMRIGHT")
    border.right:SetWidth(1)
    anchor._wqolWhiteBorder = border
end

local function EnsureLootMoverCenterGuide()
    if WipedQoL.lootMoverCenterGuide then
        return WipedQoL.lootMoverCenterGuide
    end

    local guide = CreateFrame("Frame", nil, UIParent)
    guide:SetFrameStrata("TOOLTIP")
    guide:SetPoint("TOP", UIParent, "TOP", 0, 0)
    guide:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 0)
    guide:SetWidth(1)
    guide:Hide()

    guide.line = guide:CreateTexture(nil, "OVERLAY")
    guide.line:SetAllPoints(guide)
    guide.line:SetTexture("Interface\\Buttons\\WHITE8X8")
    guide.line:SetVertexColor(1, 1, 1, 0.45)

    WipedQoL.lootMoverCenterGuide = guide
    return guide
end

local function SetLootMoverCenterGuideShown(shown)
    if shown ~= true and not WipedQoL.lootMoverCenterGuide then
        return
    end

    local guide = EnsureLootMoverCenterGuide()
    guide:SetShown(shown == true)
end

local function RefreshAnchorScaleUI(anchor)
    if not anchor or not anchor.scaleSlider then
        return
    end

    local value = math.floor(((WipedQoL:GetLootMoverScale(anchor.anchorKey) or 1) * 100) + 0.5)
    anchor.scaleSlider._wqolRefreshing = true
    anchor.scaleSlider:SetValue(value)
    anchor.scaleSlider._wqolRefreshing = false
    if anchor.scaleValue then
        anchor.scaleValue:SetText(string.format("%d%%", value))
    end
end

local function CreateAnchorScaleSlider(anchor)
    local slider = CreateFrame("Slider", nil, anchor, "BackdropTemplate")
    slider:SetSize(150, 10)
    slider:SetOrientation("HORIZONTAL")
    slider:SetMinMaxValues(50, 100)
    slider:SetValueStep(5)
    if slider.SetObeyStepOnDrag then
        slider:SetObeyStepOnDrag(true)
    end
    slider:SetHitRectInsets(0, 0, -8, -8)
    if addonTable.SkinFrame then
        addonTable.SkinFrame(slider, { light = true })
    end

    slider:SetThumbTexture("Interface\\Buttons\\WHITE8X8")
    local thumb = slider.GetThumbTexture and slider:GetThumbTexture()
    if thumb then
        thumb:SetSize(8, 16)
        if addonTable.SetAddonColorTarget then
            addonTable.SetAddonColorTarget(thumb, "SetVertexColor", 1, 1)
        elseif addonTable.GetClassColor then
            thumb:SetVertexColor(addonTable.GetClassColor())
        end
    end

    slider:SetScript("OnValueChanged", function(self, value)
        local percent = math.floor((value or 100) + 0.5)
        if anchor.scaleValue then
            anchor.scaleValue:SetText(string.format("%d%%", percent))
        end
        if self._wqolRefreshing then
            return
        end
        if IsCombatLocked() then
            WipedQoL:CloseLootMoverAnchorsForCombat()
            RefreshAnchorScaleUI(anchor)
            return
        end
        WipedQoL:SetLootMoverScale(anchor.anchorKey, percent / 100)
    end)

    return slider
end

local function SetSystemFrameScale(system, scale)
    if not system then
        return
    end

    if system.alertFramePool and system.alertFramePool.EnumerateActive then
        for alertFrame in system.alertFramePool:EnumerateActive() do
            SetFrameScale(alertFrame, scale)
        end
        return
    end

    SetFrameScale(system.alertFrame or system.anchorFrame, scale)
end

local function DisableBlizzardManagedPositions()
    local managedPositions = _G.UIPARENT_MANAGED_FRAME_POSITIONS
    if type(managedPositions) ~= "table" then
        return
    end

    for _, key in ipairs(MANAGED_POSITION_KEYS) do
        if originalManagedPositions[key] == nil then
            originalManagedPositions[key] = managedPositions[key] or false
        end
        managedPositions[key] = nil
    end
end

local function RestoreBlizzardManagedPositions()
    local managedPositions = _G.UIPARENT_MANAGED_FRAME_POSITIONS
    if type(managedPositions) ~= "table" then
        return
    end

    for _, key in ipairs(MANAGED_POSITION_KEYS) do
        local original = originalManagedPositions[key]
        if original ~= nil then
            managedPositions[key] = original ~= false and original or nil
        end
    end

    if _G.UIParent_ManageFramePositions then
        pcall(_G.UIParent_ManageFramePositions)
    end
end

local function ResetFrameScaleOverrides()
    for systemName in pairs(LOOT_TOAST_SYSTEM_NAMES) do
        SetSystemFrameScale(_G[systemName], 1)
    end
    for systemName in pairs(ACHIEVEMENT_ALERT_SYSTEM_NAMES) do
        SetSystemFrameScale(_G[systemName], 1)
    end
    for systemName in pairs(COMPLETION_ALERT_SYSTEM_NAMES) do
        SetSystemFrameScale(_G[systemName], 1)
    end
    for systemName in pairs(ACTIVITY_ALERT_SYSTEM_NAMES) do
        SetSystemFrameScale(_G[systemName], 1)
    end

    SetFrameScale(_G.GroupLootContainer, 1)
    SetFrameScale(_G.BonusRollFrame, 1)
    SetFrameScale(_G.ChallengeModeCompleteBanner, 1)

    local maxFrames = _G.NUM_GROUP_LOOT_FRAMES or 4
    for index = 1, maxFrames do
        SetFrameScale(_G["GroupLootFrame" .. index], 1)
        SetFrameScale(_G["BonusRollFrame" .. index], 1)
    end
end

local function RefreshBlizzardFramePositions()
    if _G.AlertFrame and _G.AlertFrame.UpdateAnchors then
        pcall(_G.AlertFrame.UpdateAnchors, _G.AlertFrame)
    end
    if _G.GroupLootContainer_Update then
        pcall(_G.GroupLootContainer_Update)
        pcall(_G.GroupLootContainer_Update, _G.GroupLootContainer)
    end
    if _G.BonusRollFrame and _G.BonusRollFrame_OnShow then
        pcall(_G.BonusRollFrame_OnShow, _G.BonusRollFrame)
    end
end

local function GetAlertStartAnchor(anchor, offsetY)
    if not anchor then
        return nil
    end

    local proxy = anchor._wqolStartAnchor
    if not proxy then
        proxy = CreateFrame("Frame", nil, anchor)
        proxy:SetSize(anchor:GetWidth() or ANCHORS.loot.width, 1)
        proxy:Hide()
        anchor._wqolStartAnchor = proxy
    end

    proxy:ClearAllPoints()
    proxy:SetPoint("BOTTOM", anchor, "BOTTOM", 0, ANCHOR_OFFSET_Y + (offsetY or 0))
    proxy:SetSize(anchor:GetWidth() or ANCHORS.loot.width, 1)
    return proxy
end

local function ApplyPooledAlertStack(alertFrame, matchesSystem, startAnchor, scale, orderState, includeHidden)
    orderState.activeFrames = orderState.activeFrames or {}
    orderState.activeLookup = orderState.activeLookup or {}
    local activeFrames = orderState.activeFrames
    local activeLookup = orderState.activeLookup
    local frameOrder = orderState.frames
    wipe(activeFrames)
    wipe(activeLookup)

    local function AddActiveFrame(frame)
        if not CanUseFrame(frame) or (not includeHidden and not frame:IsShown()) then
            return
        end

        activeLookup[frame] = true
        if not frameOrder[frame] then
            orderState.nextOrder = orderState.nextOrder + 1
            frameOrder[frame] = orderState.nextOrder
        end
        activeFrames[#activeFrames + 1] = frame
    end

    for _, system in ipairs(alertFrame.alertFrameSubSystems) do
        if matchesSystem(system) then
            SetSystemFrameScale(system, scale)
            if system.alertFramePool and system.alertFramePool.EnumerateActive then
                for frame in system.alertFramePool:EnumerateActive() do
                    AddActiveFrame(frame)
                end
            else
                AddActiveFrame(system.alertFrame or system.anchorFrame)
            end
        end
    end

    for frame in pairs(frameOrder) do
        if not activeLookup[frame] then
            frameOrder[frame] = nil
        end
    end

    table.sort(activeFrames, function(left, right)
        return frameOrder[left] < frameOrder[right]
    end)

    local previousFrame = startAnchor
    for index, frame in ipairs(activeFrames) do
        frame:ClearAllPoints()
        frame:SetPoint("BOTTOM", previousFrame, "TOP", 0, index == 1 and 0 or STACK_OFFSET_Y)
        previousFrame = frame
    end
end

function WipedQoL:GetLootMoverSettings()
    local modules = self.db and self.db.profile and self.db.profile.modules
    if type(modules) ~= "table" then
        return {
            enabled = false,
            unlocked = false,
        }
    end

    if type(modules.lootToasts) ~= "table" then
        modules.lootToasts = {}
    end

    local settings = modules.lootToasts
    settings.enabled = settings.enabled == true
    settings.unlocked = settings.unlocked == true
    return settings
end

function WipedQoL:GetLootToastSettings()
    return self:GetLootMoverSettings()
end

function WipedQoL:IsLootToastMoverEnabled()
    return self:GetLootMoverSettings().enabled == true
end

function WipedQoL:IsLootToastMoverActive()
    return self.lootMoverRuntimeActive == true
end

function WipedQoL:GetLootMoverScale(anchorKey)
    return self:GetLootAnchorSettings(anchorKey or "loot").scale or 1
end

function WipedQoL:SetLootMoverScale(anchorKey, scale)
    if not self:IsLootToastMoverActive() or IsCombatLocked() then
        self:CloseLootMoverAnchorsForCombat()
        return false
    end

    local settings = self:GetLootAnchorSettings(anchorKey)
    settings.scale = ClampScale(scale)

    self:ApplyLootMover()

    if self.RefreshLootToastRollingUI then
        self:RefreshLootToastRollingUI()
    end
end

function WipedQoL:GetLootAnchorSettings(anchorKey)
    local config = GetAnchorConfig(anchorKey)
    local modules = self.db and self.db.profile and self.db.profile.modules
    if type(modules) ~= "table" then
        return {
            point = config.point,
            relativePoint = config.relativePoint,
            x = config.x,
            y = config.y,
            scale = 1,
        }
    end

    if type(modules[config.key]) ~= "table" then
        modules[config.key] = {}
    end

    local settings = modules[config.key]
    local sharedScale = modules.lootToasts and modules.lootToasts.scale
    settings.point = settings.point or config.point
    settings.relativePoint = settings.relativePoint or config.relativePoint
    settings.x = tonumber(settings.x) or config.x
    settings.y = tonumber(settings.y) or config.y
    settings.scale = ClampScale(settings.scale or sharedScale)
    return settings
end

function WipedQoL:HasCustomLootAnchorSettings(anchorKey)
    local config = GetAnchorConfig(anchorKey)
    local settings = self:GetLootAnchorSettings(anchorKey)
    return settings.point ~= config.point
        or settings.relativePoint ~= config.relativePoint
        or math.abs((settings.x or 0) - config.x) > 0.5
        or math.abs((settings.y or 0) - config.y) > 0.5
        or math.abs((settings.scale or 1) - 1) > 0.001
end

function WipedQoL:HasCustomLootMoverSettings()
    for anchorKey in pairs(ANCHORS) do
        if self:HasCustomLootAnchorSettings(anchorKey) then
            return true
        end
    end
    return false
end

function WipedQoL:ShouldApplyLootMover()
    if not self:IsLootToastMoverActive() then
        return false
    end

    local settings = self:GetLootMoverSettings()
    return settings.unlocked == true or self:HasCustomLootMoverSettings()
end

function WipedQoL:SaveLootAnchorPosition(anchorKey)
    local config = GetAnchorConfig(anchorKey)
    local anchor = _G[config.name]
    if not anchor then
        return
    end

    local settings = self:GetLootAnchorSettings(anchorKey)
    local centerX, centerY = anchor:GetCenter()
    local parentCenterX, parentCenterY = UIParent:GetCenter()
    if centerX and centerY and parentCenterX and parentCenterY
        and math.abs(centerX - parentCenterX) <= CENTER_SNAP_THRESHOLD then
        settings.point = "CENTER"
        settings.relativePoint = "CENTER"
        settings.x = 0
        settings.y = centerY - parentCenterY
        return
    end

    local point, _, relativePoint, x, y = anchor:GetPoint(1)
    settings.point = point or config.point
    settings.relativePoint = relativePoint or config.relativePoint
    settings.x = x or config.x
    settings.y = y or config.y
end

function WipedQoL:RestoreLootAnchorPosition(anchorKey)
    local config = GetAnchorConfig(anchorKey)
    local anchor = self:EnsureLootAnchor(anchorKey)
    local settings = self:GetLootAnchorSettings(anchorKey)
    anchor:ClearAllPoints()
    anchor:SetPoint(settings.point, UIParent, settings.relativePoint, settings.x, settings.y)
    anchor:SetSize(config.width, config.height)
    SetFrameScale(anchor, 1)
    RefreshAnchorScaleUI(anchor)
end

function WipedQoL:EnsureLootAnchor(anchorKey)
    local config = GetAnchorConfig(anchorKey)
    local anchor = _G[config.name]
    if anchor then
        return anchor
    end

    anchor = CreateFrame("Frame", config.name, UIParent, "BackdropTemplate")
    anchor:SetSize(config.width, config.height)
    anchor:SetFrameStrata("DIALOG")
    anchor:SetMovable(true)
    anchor:SetClampedToScreen(true)
    anchor:EnableMouse(true)
    anchor:RegisterForDrag("LeftButton")
    anchor.anchorKey = anchorKey

    if addonTable.SkinFrame then
        addonTable.SkinFrame(anchor, { light = true })
    end
    AddLootMoverBorder(anchor)
    BringLootMoverToFront(anchor)

    local title = anchor:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    title:SetPoint("TOP", anchor, "TOP", 0, -10)
    title:SetText(TRANSLATE(config.label))
    if addonTable.ApplyFont then
        addonTable.ApplyFont(title, 12, "OUTLINE")
    end
    title:SetTextColor(1, 1, 1, 0.95)
    anchor.title = title

    local hint = anchor:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    hint:SetPoint("TOP", title, "BOTTOM", 0, -4)
    hint:SetText(TRANSLATE("Drag to move"))
    if addonTable.ApplyFont then
        addonTable.ApplyFont(hint, 10, "")
    end
    hint:SetTextColor(0.78, 0.78, 0.78, 0.95)
    anchor.hint = hint

    local scaleLabel = anchor:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    scaleLabel:SetPoint("TOPLEFT", anchor, "TOPLEFT", 12, -52)
    scaleLabel:SetText(TRANSLATE("Scale"))
    if addonTable.ApplyFont then
        addonTable.ApplyFont(scaleLabel, 10, "")
    end
    scaleLabel:SetTextColor(0.78, 0.78, 0.78, 0.95)
    anchor.scaleLabel = scaleLabel

    anchor.scaleSlider = CreateAnchorScaleSlider(anchor)
    anchor.scaleSlider:SetPoint("LEFT", scaleLabel, "RIGHT", 8, 0)
    anchor.scaleSlider:HookScript("OnMouseDown", function()
        BringLootMoverToFront(anchor)
    end)

    local scaleValue = anchor:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    scaleValue:SetPoint("LEFT", anchor.scaleSlider, "RIGHT", 8, 0)
    scaleValue:SetWidth(36)
    scaleValue:SetJustifyH("LEFT")
    if addonTable.ApplyFont then
        addonTable.ApplyFont(scaleValue, 10, "")
    end
    scaleValue:SetTextColor(0.82, 0.82, 0.82, 0.95)
    anchor.scaleValue = scaleValue

    anchor:SetScript("OnMouseDown", function(self)
        BringLootMoverToFront(self)
    end)
    anchor:SetScript("OnDragStart", function(self)
        if WipedQoL:IsInCombat() then
            WipedQoL:CloseLootMoverAnchorsForCombat()
            return
        end
        BringLootMoverToFront(self)
        SetLootMoverCenterGuideShown(true)
        self:StartMoving()
    end)
    anchor:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        SetLootMoverCenterGuideShown(false)
        if WipedQoL:IsInCombat() then
            WipedQoL:CloseLootMoverAnchorsForCombat()
            return
        end
        WipedQoL:SaveLootAnchorPosition(self.anchorKey)
        WipedQoL:ApplyLootMover()
    end)
    self:RestoreLootAnchorPosition(anchorKey)
    anchor:Hide()
    return anchor
end

function WipedQoL:SetLootToastMoverEnabled(enabled)
    if IsCombatLocked() then
        self:CloseLootMoverAnchorsForCombat()
        return false
    end

    local settings = self:GetLootMoverSettings()
    settings.enabled = enabled == true
    self.lootMoverReloadSuggested = settings.enabled ~= (self.lootMoverLoadedForSession == true)

    if not settings.enabled then
        self.lootMoverRuntimeActive = false
        self:CloseLootMoverAnchors()
    elseif self.lootMoverLoadedForSession == true then
        self.lootMoverRuntimeActive = true
        self:ApplyLootMover()
    end

    if self.RefreshLootToastRollingUI then
        self:RefreshLootToastRollingUI()
    end
    return true
end

function WipedQoL:SetLootToastMoverUnlocked(unlocked)
    if not self:IsLootToastMoverActive() or IsCombatLocked() then
        self:CloseLootMoverAnchorsForCombat()
        return false
    end

    local settings = self:GetLootMoverSettings()
    settings.unlocked = unlocked == true
    self:ApplyLootMover()
    if self.RefreshLootToastRollingUI then
        self:RefreshLootToastRollingUI()
    end
end

function WipedQoL:ResetLootToastMover()
    if not self:IsLootToastMoverActive() or IsCombatLocked() then
        self:CloseLootMoverAnchorsForCombat()
        return false
    end

    for anchorKey, config in pairs(ANCHORS) do
        local settings = self:GetLootAnchorSettings(anchorKey)
        settings.point = config.point
        settings.relativePoint = config.relativePoint
        settings.x = config.x
        settings.y = config.y
        settings.scale = 1
        self:RestoreLootAnchorPosition(anchorKey)
    end
    local moverSettings = self:GetLootMoverSettings()
    moverSettings.unlocked = false
    RestoreBlizzardManagedPositions()
    ResetFrameScaleOverrides()
    RefreshBlizzardFramePositions()
    self:ApplyLootMover()
end

function WipedQoL:CloseLootMoverAnchors()
    local settings = self:GetLootMoverSettings()
    settings.unlocked = false
    SetLootMoverCenterGuideShown(false)

    if self.StopLootTestMode then
        self:StopLootTestMode()
    end

    for _, config in pairs(ANCHORS) do
        local anchor = _G[config.name]
        if anchor then
            if anchor.StopMovingOrSizing then
                pcall(anchor.StopMovingOrSizing, anchor)
            end
            anchor:Hide()
        end
    end

    if self.RefreshLootToastRollingUI then
        self:RefreshLootToastRollingUI()
    end
end

function WipedQoL:CloseLootMoverAnchorsForCombat()
    self:CloseLootMoverAnchors()
end

function WipedQoL:ApplyLootToastAnchors()
    if not self:ShouldApplyLootMover() then
        return
    end

    local alertFrame = _G.AlertFrame
    if not CanUseFrame(alertFrame) or type(alertFrame.alertFrameSubSystems) ~= "table" then
        return
    end

    local anchor = self:EnsureLootAnchor("loot")
    local scale = self:GetLootMoverScale("loot")
    local startAnchor = GetAlertStartAnchor(anchor, LOOT_TOAST_MOVER_OFFSET_Y) or anchor
    ApplyPooledAlertStack(alertFrame, IsLootToastSystem, startAnchor, scale, lootToastOrderState)
end

function WipedQoL:ApplyAchievementAlertAnchors()
    if not self:ShouldApplyLootMover() then
        return
    end

    local alertFrame = _G.AlertFrame
    if not CanUseFrame(alertFrame) or type(alertFrame.alertFrameSubSystems) ~= "table" then
        return
    end

    local anchor = self:EnsureLootAnchor("achievement")
    local scale = self:GetLootMoverScale("achievement")
    local startAnchor = GetAlertStartAnchor(anchor, 0) or anchor
    ApplyPooledAlertStack(
        alertFrame,
        IsAchievementAlertSystem,
        startAnchor,
        scale,
        achievementAlertOrderState
    )
end

function WipedQoL:ApplyDungeonCompletionAlertAnchors()
    if not self:ShouldApplyLootMover() then
        return
    end

    local alertFrame = _G.AlertFrame
    if not CanUseFrame(alertFrame) or type(alertFrame.alertFrameSubSystems) ~= "table" then
        return
    end

    local anchor = self:EnsureLootAnchor("completion")
    local scale = self:GetLootMoverScale("completion")
    local startAnchor = GetAlertStartAnchor(anchor, 0) or anchor
    ApplyPooledAlertStack(
        alertFrame,
        IsCompletionAlertSystem,
        startAnchor,
        scale,
        completionAlertOrderState
    )
end

function WipedQoL:ApplyChallengeModeCompletionAnchor()
    if not self:ShouldApplyLootMover() then
        return
    end

    local frame = _G.ChallengeModeCompleteBanner
    if not CanUseFrame(frame) then
        return
    end

    local anchor = self:EnsureLootAnchor("challenge")
    SetFrameScale(frame, self:GetLootMoverScale("challenge"))
    frame:ClearAllPoints()
    frame:SetPoint("TOP", anchor, "TOP", 0, 0)
end

function WipedQoL:ApplyMonthlyActivityAlertAnchors()
    if not self:ShouldApplyLootMover() then
        return
    end

    local alertFrame = _G.AlertFrame
    if not CanUseFrame(alertFrame) or type(alertFrame.alertFrameSubSystems) ~= "table" then
        return
    end

    local anchor = self:EnsureLootAnchor("activity")
    local scale = self:GetLootMoverScale("activity")
    local startAnchor = GetAlertStartAnchor(anchor, 0) or anchor
    ApplyPooledAlertStack(
        alertFrame,
        IsActivityAlertSystem,
        startAnchor,
        scale,
        activityAlertOrderState
    )
end

function WipedQoL:ApplyLootRollAnchors()
    if not self:ShouldApplyLootMover() then
        return
    end

    local rollAnchor = self:EnsureLootAnchor("roll")
    local scale = self:GetLootMoverScale("roll")
    local groupLootContainer = _G.GroupLootContainer
    if CanUseFrame(groupLootContainer) then
        SetFrameScale(groupLootContainer, scale)
        groupLootContainer:ClearAllPoints()
        groupLootContainer:SetPoint("BOTTOMLEFT", rollAnchor, "BOTTOMLEFT", ANCHOR_OFFSET_X, ANCHOR_OFFSET_Y)
    end

    local maxFrames = _G.NUM_GROUP_LOOT_FRAMES or 4
    for index = 1, maxFrames do
        local frame = _G["GroupLootFrame" .. index]
        if CanUseFrame(frame) then
            SetFrameScale(frame, scale)
            frame:ClearAllPoints()
            if index == 1 then
                frame:SetPoint("BOTTOMLEFT", rollAnchor, "BOTTOMLEFT", ANCHOR_OFFSET_X, ANCHOR_OFFSET_Y)
            else
                frame:SetPoint("BOTTOM", _G["GroupLootFrame" .. (index - 1)], "TOP", 0, STACK_OFFSET_Y)
            end
        end
    end
end

function WipedQoL:ApplyBonusRollAnchor()
    if not self:ShouldApplyLootMover() then
        return
    end

    local bonusAnchor = self:EnsureLootAnchor("bonus")
    local scale = self:GetLootMoverScale("bonus")
    local bonusRollFrame = _G.BonusRollFrame
    if CanUseFrame(bonusRollFrame) then
        SetFrameScale(bonusRollFrame, scale)
        bonusRollFrame:ClearAllPoints()
        bonusRollFrame:SetPoint("BOTTOMLEFT", bonusAnchor, "BOTTOMLEFT", ANCHOR_OFFSET_X, ANCHOR_OFFSET_Y)
    end

    local maxFrames = _G.NUM_GROUP_LOOT_FRAMES or 4
    for index = 1, maxFrames do
        local frame = _G["BonusRollFrame" .. index]
        if CanUseFrame(frame) then
            SetFrameScale(frame, scale)
            frame:ClearAllPoints()
            if index == 1 then
                frame:SetPoint("BOTTOM", bonusRollFrame or bonusAnchor, "TOP", 0, STACK_OFFSET_Y)
            else
                frame:SetPoint("BOTTOM", _G["BonusRollFrame" .. (index - 1)], "TOP", 0, STACK_OFFSET_Y)
            end
        end
    end
end

function WipedQoL:ApplyLootMover()
    if not self:IsLootToastMoverActive() then
        return
    end

    if IsCombatLocked() then
        self:CloseLootMoverAnchorsForCombat()
        return
    end

    self:HookLootToastAlertFrames()

    local settings = self:GetLootMoverSettings()
    for anchorKey in pairs(ANCHORS) do
        self:RestoreLootAnchorPosition(anchorKey)
        self:EnsureLootAnchor(anchorKey):SetShown(settings.unlocked)
    end

    if not self:ShouldApplyLootMover() then
        RestoreBlizzardManagedPositions()
        ResetFrameScaleOverrides()
        RefreshBlizzardFramePositions()
        return
    end

    DisableBlizzardManagedPositions()

    if _G.AlertFrame and _G.AlertFrame.UpdateAnchors then
        _G.AlertFrame:UpdateAnchors()
    end

    self:ApplyLootToastAnchors()
    self:ApplyAchievementAlertAnchors()
    self:ApplyDungeonCompletionAlertAnchors()
    self:ApplyChallengeModeCompletionAnchor()
    self:ApplyMonthlyActivityAlertAnchors()
    self:ApplyLootRollAnchors()
    self:ApplyBonusRollAnchor()

    if self.RefreshLootTestPreviews then
        self:RefreshLootTestPreviews()
    end
end

function WipedQoL:ApplyLootToastMover()
    self:ApplyLootMover()
end

local function ApplyMovedAlertAnchors()
    WipedQoL:ApplyLootToastAnchors()
    WipedQoL:ApplyAchievementAlertAnchors()
    WipedQoL:ApplyDungeonCompletionAlertAnchors()
    WipedQoL:ApplyMonthlyActivityAlertAnchors()
end

local function HookAlertSystemAddAlert(systemName)
    local system = _G[systemName]
    if not system or system._wqolMoverAlertHooked or not hooksecurefunc then
        return
    end

    if system.AddAlert then
        hooksecurefunc(system, "AddAlert", function()
            if C_Timer then
                C_Timer.After(0, function()
                    ApplyMovedAlertAnchors()
                end)
            else
                ApplyMovedAlertAnchors()
            end
        end)
        system._wqolMoverAlertHooked = true
    end
end

local function ApplyAchievementAlertAnchorsBeforeShow()
    if not WipedQoL:ShouldApplyLootMover() then
        return false
    end

    local alertFrame = _G.AlertFrame
    if not CanUseFrame(alertFrame) or type(alertFrame.alertFrameSubSystems) ~= "table" then
        return false
    end

    local anchor = WipedQoL:EnsureLootAnchor("achievement")
    local scale = WipedQoL:GetLootMoverScale("achievement")
    local startAnchor = GetAlertStartAnchor(anchor, 0) or anchor
    ApplyPooledAlertStack(
        alertFrame,
        IsAchievementAlertSystem,
        startAnchor,
        scale,
        achievementAlertOrderState,
        true
    )
    return true
end

local function HookAchievementAlertSystemAnchors(systemName)
    local system = _G[systemName]
    if not system or system._wqolMoverAdjustAnchorsHooked or not system.AdjustAnchors then
        return
    end

    local originalAdjustAnchors = system.AdjustAnchors
    system.AdjustAnchors = function(alertSystem, relativeAlert)
        -- Blizzard calls AdjustAnchors after acquiring the pooled frame,
        -- but before showing it. Include that hidden frame immediately.
        if ApplyAchievementAlertAnchorsBeforeShow() then
            return relativeAlert
        end

        return originalAdjustAnchors(alertSystem, relativeAlert)
    end
    system._wqolMoverAdjustAnchorsHooked = true
end

function WipedQoL:HookLootToastAlertFrames()
    if not hooksecurefunc then
        return
    end

    if not self.lootToastFixAnchorsHooked and _G.AlertFrame_FixAnchors then
        hooksecurefunc("AlertFrame_FixAnchors", function()
            ApplyMovedAlertAnchors()
        end)
        self.lootToastFixAnchorsHooked = true
    end

    if not self.lootToastUpdateAnchorsHooked and _G.AlertFrame and _G.AlertFrame.UpdateAnchors then
        hooksecurefunc(_G.AlertFrame, "UpdateAnchors", function()
            ApplyMovedAlertAnchors()
        end)
        self.lootToastUpdateAnchorsHooked = true
    end

    if not self.achievementAlertShowHooked and _G.AlertFrame_ShowNewAlert then
        hooksecurefunc("AlertFrame_ShowNewAlert", function(frame)
            if frame and IsAchievementAlertSystem(frame.queue) then
                WipedQoL:ApplyAchievementAlertAnchors()
            elseif frame and IsCompletionAlertSystem(frame.queue) then
                WipedQoL:ApplyDungeonCompletionAlertAnchors()
            elseif frame and IsActivityAlertSystem(frame.queue) then
                WipedQoL:ApplyMonthlyActivityAlertAnchors()
            end
        end)
        self.achievementAlertShowHooked = true
    end

    if not self.challengeModeCompletionHooked and _G.TopBannerManager_Show then
        hooksecurefunc("TopBannerManager_Show", function(frame)
            if frame == _G.ChallengeModeCompleteBanner then
                WipedQoL:ApplyChallengeModeCompletionAnchor()
            end
        end)
        self.challengeModeCompletionHooked = true
    end

    local hooks = {
        "GroupLootContainer_RemoveFrame",
        "GroupLootContainer_Update",
        "GroupLootFrame_OnShow",
        "BonusRollFrame_StartBonusRoll",
        "BonusRollFrame_OnUpdate",
        "BonusRollFrame_OnShow",
    }
    for _, hookName in ipairs(hooks) do
        if _G[hookName] and not self["lootMoverHooked_" .. hookName] then
            hooksecurefunc(hookName, function()
                WipedQoL:ApplyLootRollAnchors()
                WipedQoL:ApplyBonusRollAnchor()
            end)
            self["lootMoverHooked_" .. hookName] = true
        end
    end

    for systemName in pairs(LOOT_TOAST_SYSTEM_NAMES) do
        HookAlertSystemAddAlert(systemName)
    end
    for systemName in pairs(ACHIEVEMENT_ALERT_SYSTEM_NAMES) do
        HookAlertSystemAddAlert(systemName)
        HookAchievementAlertSystemAnchors(systemName)
    end
    for systemName in pairs(COMPLETION_ALERT_SYSTEM_NAMES) do
        HookAlertSystemAddAlert(systemName)
    end
    for systemName in pairs(ACTIVITY_ALERT_SYSTEM_NAMES) do
        HookAlertSystemAddAlert(systemName)
    end
end

function WipedQoL:SetupLootToastMover()
    if not self:IsLootToastMoverActive() or self.lootToastMoverSetupDone then
        return
    end
    self.lootToastMoverSetupDone = true

    for anchorKey in pairs(ANCHORS) do
        self:EnsureLootAnchor(anchorKey)
    end

    self:HookLootToastAlertFrames()
    self:ApplyLootMover()
end

function WipedQoL:InitializeLootToastMover()
    local enabled = self:IsLootToastMoverEnabled()
    self.lootMoverLoadedForSession = enabled
    self.lootMoverRuntimeActive = enabled
    self.lootMoverReloadSuggested = false

    if enabled then
        self:SetupLootToastMover()
    end
end

local function TryAddAlert(system, ...)
    if not system or not system.AddAlert then
        return false
    end
    local ok = pcall(system.AddAlert, system, ...)
    return ok == true
end

local function CreateDungeonCompletionTestPreview()
    local frame = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
    frame:SetSize(336, 80)
    frame:SetFrameStrata("DIALOG")
    frame:EnableMouse(false)
    if addonTable.SkinFrame then
        addonTable.SkinFrame(frame, { light = true })
    end

    local icon = frame:CreateTexture(nil, "ARTWORK")
    icon:SetPoint("LEFT", frame, "LEFT", 12, 0)
    icon:SetSize(52, 52)
    icon:SetTexture("Interface\\Icons\\Achievement_Dungeon_GloryoftheHERO")

    local title = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    title:SetPoint("TOPLEFT", icon, "TOPRIGHT", 12, -3)
    title:SetPoint("RIGHT", frame, "RIGHT", -12, 0)
    title:SetJustifyH("LEFT")
    title:SetText(TRANSLATE("Dungeon complete"))
    if addonTable.ApplyFont then
        addonTable.ApplyFont(title, 12, "OUTLINE")
    end

    local name = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    name:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8)
    name:SetPoint("RIGHT", frame, "RIGHT", -12, 0)
    name:SetJustifyH("LEFT")
    name:SetText(TRANSLATE("Test Dungeon"))
    name:SetTextColor(1, 0.82, 0.2, 1)
    if addonTable.ApplyFont then
        addonTable.ApplyFont(name, 14, "")
    end

    frame:Hide()
    return frame
end

local function CreateChallengeModeCompletionTestPreview()
    local frame = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
    frame:SetSize(360, 220)
    frame:SetFrameStrata("DIALOG")
    frame:EnableMouse(false)
    if addonTable.SkinFrame then
        addonTable.SkinFrame(frame, { light = true })
    end

    local accent = frame:CreateTexture(nil, "ARTWORK")
    accent:SetPoint("TOPLEFT", frame, "TOPLEFT", 28, -48)
    accent:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -28, -48)
    accent:SetHeight(2)
    accent:SetColorTexture(1, 0.72, 0.08, 0.85)

    local level = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    level:SetPoint("TOP", frame, "TOP", 0, -15)
    level:SetText("+12")
    level:SetTextColor(1, 0.82, 0.2, 1)
    if addonTable.ApplyFont then
        addonTable.ApplyFont(level, 24, "OUTLINE")
    end

    local title = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    title:SetPoint("TOP", accent, "BOTTOM", 0, -18)
    title:SetText(TRANSLATE("Test Mythic+ Dungeon"))
    if addonTable.ApplyFont then
        addonTable.ApplyFont(title, 18, "OUTLINE")
    end

    local completed = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    completed:SetPoint("TOP", title, "BOTTOM", 0, -14)
    completed:SetText(TRANSLATE("Mythic+ complete"))
    completed:SetTextColor(0.3, 1, 0.42, 1)
    if addonTable.ApplyFont then
        addonTable.ApplyFont(completed, 13, "")
    end

    local upgraded = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    upgraded:SetPoint("TOP", completed, "BOTTOM", 0, -8)
    upgraded:SetText(TRANSLATE("Keystone upgraded"))
    if addonTable.ApplyFont then
        addonTable.ApplyFont(upgraded, 12, "")
    end

    frame:Hide()
    return frame
end

local function CreateMonthlyActivityTestPreview()
    local frame = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
    frame:SetSize(256, 84)
    frame:SetFrameStrata("DIALOG")
    frame:EnableMouse(false)
    if addonTable.SkinFrame then
        addonTable.SkinFrame(frame, { light = true })
    end

    local icon = frame:CreateTexture(nil, "ARTWORK")
    icon:SetPoint("LEFT", frame, "LEFT", 10, 0)
    icon:SetSize(50, 50)
    if icon.SetAtlas then
        icon:SetAtlas("activities-toast-icon")
    end

    local title = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    title:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, -2)
    title:SetPoint("RIGHT", frame, "RIGHT", -10, 0)
    title:SetJustifyH("CENTER")
    title:SetText(TRANSLATE("Activity progressed"))
    title:SetTextColor(1, 0.82, 0.2, 1)
    if addonTable.ApplyFont then
        addonTable.ApplyFont(title, 11, "OUTLINE")
    end

    local name = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    name:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -9)
    name:SetPoint("RIGHT", frame, "RIGHT", -10, 0)
    name:SetJustifyH("CENTER")
    name:SetText(TRANSLATE("Test journal activity"))
    if addonTable.ApplyFont then
        addonTable.ApplyFont(name, 12, "")
    end

    frame:Hide()
    return frame
end

local function CreateTestPreview(kind)
    if kind == "completion" then
        return CreateDungeonCompletionTestPreview()
    elseif kind == "challenge" then
        return CreateChallengeModeCompletionTestPreview()
    elseif kind == "activity" then
        return CreateMonthlyActivityTestPreview()
    end

    local isBonus = kind == "bonus"
    local frame = CreateFrame("Frame", nil, UIParent, "BackdropTemplate")
    frame:SetSize(269, isBonus and 72 or 62)
    frame:SetFrameStrata("DIALOG")
    frame:EnableMouse(false)
    if addonTable.SkinFrame then
        addonTable.SkinFrame(frame, { light = true })
    end

    local icon = frame:CreateTexture(nil, "ARTWORK")
    icon:SetPoint("LEFT", frame, "LEFT", 9, 0)
    icon:SetSize(42, 42)
    icon:SetTexture(C_Item and C_Item.GetItemIconByID and C_Item.GetItemIconByID(TEST_ITEM_ID) or 134400)

    local title = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    title:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, -1)
    title:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -10, -11)
    title:SetJustifyH("LEFT")
    title:SetText(TRANSLATE(isBonus and "Test bonus roll" or "Test loot roll"))
    if addonTable.ApplyFont then
        addonTable.ApplyFont(title, 12, "")
    end

    local itemName = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    itemName:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -5)
    itemName:SetPoint("TOPRIGHT", title, "BOTTOMRIGHT", 0, -5)
    itemName:SetJustifyH("LEFT")
    itemName:SetText(TRANSLATE("Test Rare Loot"))
    itemName:SetTextColor(0, 0.44, 0.87, 1)
    if addonTable.ApplyFont then
        addonTable.ApplyFont(itemName, 11, "")
    end

    local progress = frame:CreateTexture(nil, "ARTWORK")
    progress:SetPoint("BOTTOMLEFT", icon, "BOTTOMRIGHT", 10, 2)
    progress:SetHeight(4)
    progress:SetColorTexture(0.82, 0.68, 0.12, 0.9)

    if isBonus then
        progress:SetPoint("RIGHT", frame, "RIGHT", -76, 0)

        local rollButton = CreateFrame("Button", nil, frame)
        rollButton:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -9, 8)
        rollButton:SetSize(58, 22)
        rollButton:EnableMouse(false)
        if addonTable.SkinButton then
            addonTable.SkinButton(rollButton)
        end
        local rollText = rollButton:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        rollText:SetPoint("CENTER")
        rollText:SetText(TRANSLATE("Roll"))
        if addonTable.ApplyFont then
            addonTable.ApplyFont(rollText, 10, "")
        end
    else
        progress:SetPoint("RIGHT", frame, "RIGHT", -92, 0)
        local labels = { "N", "G", "P" }
        for index, label in ipairs(labels) do
            local choice = CreateFrame("Frame", nil, frame, "BackdropTemplate")
            choice:SetSize(22, 22)
            choice:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -8 - ((3 - index) * 26), 7)
            if addonTable.SkinFrame then
                addonTable.SkinFrame(choice, { light = true })
            end
            local choiceText = choice:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            choiceText:SetPoint("CENTER")
            choiceText:SetText(label)
            if addonTable.ApplyFont then
                addonTable.ApplyFont(choiceText, 10, "")
            end
        end
    end

    frame:Hide()
    return frame
end

function WipedQoL:EnsureLootTestPreview(kind)
    self.lootTestPreviews = self.lootTestPreviews or {}
    if not self.lootTestPreviews[kind] then
        self.lootTestPreviews[kind] = CreateTestPreview(kind)
    end
    return self.lootTestPreviews[kind]
end

function WipedQoL:RefreshLootTestPreviews()
    local active = self:IsLootTestModeActive()
    for _, kind in ipairs({ "roll", "bonus", "completion", "challenge", "activity" }) do
        local preview = self:EnsureLootTestPreview(kind)
        if active then
            local anchor = self:EnsureLootAnchor(kind)
            preview:ClearAllPoints()
            if kind == "challenge" then
                preview:SetPoint("TOP", anchor, "TOP", 0, 0)
            elseif kind == "completion" or kind == "activity" then
                preview:SetPoint("BOTTOM", anchor, "BOTTOM", 0, ANCHOR_OFFSET_Y)
            else
                preview:SetPoint("BOTTOMLEFT", anchor, "BOTTOMLEFT", ANCHOR_OFFSET_X, ANCHOR_OFFSET_Y)
            end
            preview:SetFrameLevel(anchor:GetFrameLevel() + 5)
            SetFrameScale(preview, self:GetLootMoverScale(kind))
            preview:Show()
        else
            preview:Hide()
        end
    end
end

function WipedQoL:IsLootTestModeActive()
    return self.lootTestModeToken ~= nil
end

function WipedQoL:StopLootTestMode()
    if not self.lootTestModeToken then
        return
    end

    self.lootTestModeToken = nil
    if self.lootTestPreviews then
        for _, preview in pairs(self.lootTestPreviews) do
            preview:Hide()
        end
    end
    if self.RefreshLootToastRollingUI then
        self:RefreshLootToastRollingUI()
    end
end

function WipedQoL:StartLootTestMode()
    if not self:IsLootToastMoverActive() or IsCombatLocked() then
        self:StopLootTestMode()
        return false
    end

    self:SetupLootToastMover()
    local token = {}
    self.lootTestModeToken = token
    self.lootTestModeNextKindIndex = 1
    self:RefreshLootTestPreviews()
    self:ShowAchievementAlertTest(1, true, token)

    local function TriggerNextAlert()
        if WipedQoL.lootTestModeToken ~= token then
            return
        end
        if IsCombatLocked() then
            WipedQoL:StopLootTestMode()
            return
        end

        local kindIndex = WipedQoL.lootTestModeNextKindIndex or 1
        local kind = TEST_ALERT_KINDS[kindIndex]
        WipedQoL.lootTestModeNextKindIndex = (kindIndex % #TEST_ALERT_KINDS) + 1
        local popupCount = math.random(1, 3)
        for index = 1, popupCount do
            local delay = (index - 1) * 0.12
            if delay > 0 and C_Timer and C_Timer.After then
                C_Timer.After(delay, function()
                    WipedQoL:ShowLootToastTest(kind, 1, true, token)
                end)
            else
                WipedQoL:ShowLootToastTest(kind, 1, true, token)
            end
        end

        if C_Timer and C_Timer.After then
            C_Timer.After(TEST_MODE_INTERVAL, TriggerNextAlert)
        end
    end

    TriggerNextAlert()
    if self.RefreshLootToastRollingUI then
        self:RefreshLootToastRollingUI()
    end
    return true
end

function WipedQoL:SetLootTestModeEnabled(enabled)
    if enabled == true then
        return self:StartLootTestMode()
    end
    self:StopLootTestMode()
    return true
end

function WipedQoL:TryShowLootToastTest(kind)
    if kind == "upgrade" then
        local system = _G.LootUpgradeAlertSystem
        local specID = GetPlayerSpecID()
        local sex = UnitSex and UnitSex("player") or 2
        local playerName = UnitName and UnitName("player") or "Player"
        local classID = GetPlayerClassID()
        local itemLink = BuildTestItemLink("a335ee", "Test Upgrade Loot")

        return TryAddAlert(system, itemLink, 1, specID, sex, playerName, classID, 1)
            or TryAddAlert(system, itemLink, 1, specID)
            or TryAddAlert(system, itemLink, 1)
    end

    local system = _G.LootAlertSystem
    local itemLink = BuildTestItemLink("0070dd", "Test Rare Loot")
    return TryAddAlert(system, itemLink, 1)
        or TryAddAlert(system, itemLink, 1, nil, nil, nil, false)
end

function WipedQoL:TryShowAchievementAlertTest()
    if not _G.AchievementFrame then
        if _G.AchievementFrame_LoadUI then
            pcall(_G.AchievementFrame_LoadUI)
        elseif C_AddOns and C_AddOns.LoadAddOn then
            pcall(C_AddOns.LoadAddOn, "Blizzard_AchievementUI")
        end
    end
    self:HookLootToastAlertFrames()
    return TryAddAlert(_G.AchievementAlertSystem, TEST_ACHIEVEMENT_ID)
end

function WipedQoL:ShowAchievementAlertTest(attempt, silent, testToken)
    if not self:IsLootToastMoverActive()
        or IsCombatLocked()
        or (testToken and self.lootTestModeToken ~= testToken) then
        return false
    end

    self:SetupLootToastMover()
    if self:TryShowAchievementAlertTest() then
        self:ApplyAchievementAlertAnchors()
        return true
    end

    attempt = tonumber(attempt) or 1
    if attempt < 3 and C_Timer and C_Timer.After then
        C_Timer.After(0.15, function()
            WipedQoL:ShowAchievementAlertTest(attempt + 1, silent, testToken)
        end)
        return false
    end

    if not silent then
        self:Print(TRANSLATE("Achievement popup test could not be shown."))
    end
    return false
end

function WipedQoL:ShowLootToastTest(kind, attempt, silent, testToken)
    if not self:IsLootToastMoverActive()
        or IsCombatLocked()
        or (testToken and self.lootTestModeToken ~= testToken) then
        return false
    end

    self:SetupLootToastMover()
    self:HookLootToastAlertFrames()

    if self:TryShowLootToastTest(kind) then
        self:ApplyLootToastAnchors()
        return true
    end

    attempt = tonumber(attempt) or 1
    if attempt < 3 and C_Timer then
        C_Timer.After(0.15, function()
            WipedQoL:ShowLootToastTest(kind, attempt + 1, silent, testToken)
        end)
        return false
    end

    if not silent then
        self:Print(TRANSLATE("Loot popup test could not be shown."))
    end
    return false
end
