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

local EYE_TEXTURE = "Interface\\CHARACTERFRAME\\TempPortraitAlphaMask"
local WHITE_TEXTURE = "Interface\\Buttons\\WHITE8X8"
local PLAY_TEXTURE = "Interface\\AddOns\\WipedQoL\\Icons\\play"
local UPDATE_INTERVAL = 1 / 30
local SCENARIO_CHECK_INTERVAL = 0.25
local STEP_DURATION = 0.68
local STATUS_PULSE_SPEED = 4
local APPLICANT_PULSE_DURATION = (math.pi * 2) / STATUS_PULSE_SPEED
local APPLICANT_PULSE_COUNT = 6
local INDICATOR_ART_SIZE = 34
local INDICATOR_VISIBLE_SIZE = 28
local INDICATOR_BASE_SIZE = 42
local INDICATOR_BASE_SCALE = INDICATOR_BASE_SIZE / INDICATOR_ART_SIZE
local INDICATOR_HOLDER_SIZE = 62
local ELLESMERE_MINIMAP_ADDON = "EllesmereUIMinimap"
local ELLESMERE_QUEUE_BUTTON_NAME = "WipedQoLQueueMinimapButton"
local ELLESMERE_FIRST_ORDER = 1000000
local VALID_DOCKS = {
    TOPLEFT = true,
    TOP = true,
    TOPRIGHT = true,
    LEFT = true,
    CENTER = true,
    RIGHT = true,
    BOTTOMLEFT = true,
    BOTTOM = true,
    BOTTOMRIGHT = true,
}

local function IsAddOnCurrentlyLoaded(addonName)
    if C_AddOns and C_AddOns.IsAddOnLoaded then
        return C_AddOns.IsAddOnLoaded(addonName)
    end
    return _G.IsAddOnLoaded and _G.IsAddOnLoaded(addonName)
end

local function GetEllesmereMinimapProfile()
    if not IsAddOnCurrentlyLoaded(ELLESMERE_MINIMAP_ADDON) then
        return nil
    end

    local ellesmere = _G.EllesmereUI
    local lite = ellesmere and ellesmere.Lite
    if not lite or not lite.GetAddon then
        return nil
    end

    local minimapAddon = lite.GetAddon(ELLESMERE_MINIMAP_ADDON, true)
    local profile = minimapAddon and minimapAddon.db and minimapAddon.db.profile
    return profile and profile.minimap
end

local function ClampScale(value)
    value = tonumber(value) or 1
    return math.max(0.5, math.min(1, value))
end

local function IsSecret(value)
    return issecretvalue and issecretvalue(value) == true
end

local function IsLFGCategoryInProgress(category)
    if not GetLFGMode or not category then
        return false
    end

    local mode = GetLFGMode(category)
    return not IsSecret(mode) and mode == "lfgparty"
end

local function IsBattlefieldInProgress()
    if not GetBattlefieldStatus then
        return false
    end

    local maxBattlefields = GetMaxBattlefieldID and GetMaxBattlefieldID()
        or _G.MAX_BATTLEFIELD_QUEUES
        or 3
    for index = 1, maxBattlefields do
        local status = GetBattlefieldStatus(index)
        if not IsSecret(status) and status == "active" then
            return true
        end
    end
    return false
end

local function IsActivityInProgress()
    -- Blizzard uses "lfgparty" for active matched dungeons, Raid Finder
    -- wings and scenarios.
    if IsLFGCategoryInProgress(_G.LE_LFG_CATEGORY_LFD)
        or IsLFGCategoryInProgress(_G.LE_LFG_CATEGORY_RF)
        or IsLFGCategoryInProgress(_G.LE_LFG_CATEGORY_SCENARIO) then
        return true
    end

    if C_PartyInfo and C_PartyInfo.IsDelveInProgress
        and C_PartyInfo.IsDelveInProgress() then
        return true
    end

    if C_ChallengeMode and C_ChallengeMode.IsChallengeModeActive
        and C_ChallengeMode.IsChallengeModeActive() then
        return true
    end

    if IsBattlefieldInProgress() then
        return true
    end

    if C_PetBattles and C_PetBattles.IsInBattle
        and C_PetBattles.IsInBattle() then
        return true
    end

    -- Some scenario-style activities do not expose an LFG category.
    if C_Scenario and C_Scenario.IsInScenario and C_Scenario.IsInScenario()
        and IsInInstance then
        local inInstance, instanceType = IsInInstance()
        return inInstance == true and instanceType == "scenario"
    end

    return false
end

local function SuppressBlizzardQueueArtwork(button)
    local display = button._wqolQueueDisplay
    if not display or not display.enabled then
        return
    end

    if button.Highlight then
        button.Highlight:Hide()
    end
    if button.Eye then
        if display.originalEyeAlpha == nil then
            display.originalEyeAlpha = button.Eye:GetAlpha()
        end
        button.Eye:SetAlpha(0)
        if button.Eye.SetStaticMode and not button.Eye:IsStaticMode() then
            display.applyingStaticEye = true
            button.Eye:SetStaticMode(true)
            display.applyingStaticEye = nil
        end
    end
end

local function RestoreBlizzardQueueArtwork(button)
    local display = button._wqolQueueDisplay
    if not display then
        return
    end

    if button.Eye and display.originalEyeAlpha ~= nil then
        button.Eye:SetAlpha(display.originalEyeAlpha)
        display.originalEyeAlpha = nil
    end
    if button.UpdateGlow then
        button:UpdateGlow()
    end
    if _G.QueueStatusFrame and _G.QueueStatusFrame.Update then
        _G.QueueStatusFrame:Update()
    end
end

local function SetIndicatorColor(display, ready)
    display.ready = ready == true
    local r, g, b
    if display.ready then
        r, g, b = 0.3, 1, 0.42
    else
        r, g, b = 1, 0.72, 0.08
    end

    display.visibleCount = nil
    display.readyLayout = nil
    for _, edge in pairs(display.glowEdges) do
        edge:SetVertexColor(r, g, b)
    end
    for _, person in ipairs(display.people) do
        person.head:SetVertexColor(r, g, b)
        person.shoulders:SetVertexColor(r, g, b)
    end
end

local function CreateQueueIndicatorPreview(parent, state)
    local preview = CreateFrame("Frame", nil, parent)
    preview:SetSize(INDICATOR_ART_SIZE, INDICATOR_ART_SIZE)

    local background = preview:CreateTexture(nil, "BACKGROUND")
    background:SetPoint("TOPLEFT", 3, -3)
    background:SetPoint("BOTTOMRIGHT", -3, 3)
    background:SetColorTexture(0, 0, 0, 1)

    preview.glowEdges = {
        top = preview:CreateTexture(nil, "BORDER"),
        bottom = preview:CreateTexture(nil, "BORDER"),
        left = preview:CreateTexture(nil, "BORDER"),
        right = preview:CreateTexture(nil, "BORDER"),
    }
    preview.glowEdges.top:SetSize(28, 2)
    preview.glowEdges.top:SetPoint("CENTER", preview, "CENTER", 0, 13)
    preview.glowEdges.bottom:SetSize(28, 2)
    preview.glowEdges.bottom:SetPoint("CENTER", preview, "CENTER", 0, -13)
    preview.glowEdges.left:SetSize(2, 28)
    preview.glowEdges.left:SetPoint("CENTER", preview, "CENTER", -13, 0)
    preview.glowEdges.right:SetSize(2, 28)
    preview.glowEdges.right:SetPoint("CENTER", preview, "CENTER", 13, 0)

    local ready = state == "ready"
    local scenario = state == "scenario"
    local green = ready or scenario
    local r, g, b = green and 0.3 or 1, green and 1 or 0.72, green and 0.42 or 0.08
    for _, edge in pairs(preview.glowEdges) do
        edge:SetTexture(WHITE_TEXTURE)
        edge:SetBlendMode("ADD")
        edge:SetVertexColor(r, g, b)
        edge:SetAlpha(0)
    end

    preview.people = {}
    for index = 1, 3 do
        local person = {}
        person.head = preview:CreateTexture(nil, "ARTWORK", nil, 6 - index)
        person.head:SetTexture(EYE_TEXTURE)
        person.head:SetVertexColor(r, g, b)
        person.shoulders = preview:CreateTexture(nil, "ARTWORK", nil, 6 - index)
        person.shoulders:SetTexture(EYE_TEXTURE)
        person.shoulders:SetVertexColor(r, g, b)
        preview.people[index] = person
    end

    local scenarioLabel = CreateFrame("Frame", nil, preview)
    scenarioLabel:SetSize(16, 16)
    scenarioLabel:SetPoint("CENTER", 1, 0)
    local scenarioIcon = scenarioLabel:CreateTexture(nil, "OVERLAY")
    scenarioIcon:SetAllPoints()
    scenarioIcon:SetTexture(PLAY_TEXTURE)
    scenarioIcon:SetVertexColor(0.3, 1, 0.42, 1)
    scenarioLabel:SetShown(scenario)
    preview.scenarioLabel = scenarioLabel

    local function SetVisibleCount(count)
        if preview.visibleCount == count then
            return
        end
        preview.visibleCount = count
        local spread = count == 1 and 0 or (count == 2 and 4 or 7)
        local step = count > 1 and (spread * 2 / (count - 1)) or 0
        for index, person in ipairs(preview.people) do
            local x = spread - ((index - 1) * step)
            local y = -spread * 0.55 + ((index - 1) * step * 0.55)
            person.head:ClearAllPoints()
            person.head:SetPoint("CENTER", preview, "CENTER", x, y + 4)
            person.shoulders:ClearAllPoints()
            person.shoulders:SetPoint("CENTER", preview, "CENTER", x, y - 4)
            person.head:SetSize(6, 6)
            person.shoulders:SetSize(10, 6)
            person.head:SetAlpha(index <= count and (1 - ((index - 1) * 0.2)) or 0)
            person.shoulders:SetAlpha(index <= count and (0.9 - ((index - 1) * 0.18)) or 0)
        end
    end

    if scenario then
        for _, person in ipairs(preview.people) do
            person.head:SetAlpha(0)
            person.shoulders:SetAlpha(0)
        end
    elseif ready then
        local person = preview.people[1]
        person.head:SetPoint("CENTER", preview, "CENTER", 0, 4)
        person.shoulders:SetPoint("CENTER", preview, "CENTER", 0, -4)
        preview.people[2].head:SetAlpha(0)
        preview.people[2].shoulders:SetAlpha(0)
        preview.people[3].head:SetAlpha(0)
        preview.people[3].shoulders:SetAlpha(0)
    else
        SetVisibleCount(1)
    end

    preview.elapsed = 0
    preview:SetScript("OnUpdate", function(self, delta)
        self.elapsed = self.elapsed + delta
        if ready then
            local pulse = (math.sin(self.elapsed * STATUS_PULSE_SPEED) + 1) * 0.5
            local person = self.people[1]
            person.head:SetAlpha(0.68 + pulse * 0.32)
            person.shoulders:SetAlpha((0.68 + pulse * 0.32) * 0.92)
            person.head:SetSize(6.5 + pulse * 1.5, 6.5 + pulse * 1.5)
            person.shoulders:SetSize(10.5 + pulse * 2, 6.5 + pulse * 1.5)
            for _, edge in pairs(self.glowEdges) do
                edge:SetAlpha(0.25 + pulse * 0.55)
            end
            return
        end
        if scenario then
            local pulse = (math.sin(self.elapsed * STATUS_PULSE_SPEED) + 1) * 0.5
            self.scenarioLabel:SetAlpha(0.55 + pulse * 0.45)
            self.scenarioLabel:SetScale(0.9 + pulse * 0.15)
            for _, edge in pairs(self.glowEdges) do
                edge:SetAlpha(0.8)
            end
            return
        end

        SetVisibleCount((math.floor(self.elapsed / STEP_DURATION) % 3) + 1)
        local alertPulse = 0
        if state == "applicant" then
            local sequenceDuration = APPLICANT_PULSE_DURATION * APPLICANT_PULSE_COUNT
            local sequenceTime = self.elapsed % (sequenceDuration + 0.8)
            if sequenceTime < sequenceDuration then
                local cycle = sequenceTime / APPLICANT_PULSE_DURATION
                alertPulse = math.sin(math.pi * (cycle % 1))
            end
        end
        for _, edge in pairs(self.glowEdges) do
            edge:SetAlpha(alertPulse * 0.8)
        end
    end)

    return preview
end

function WipedQoL:ShowQueueIndicatorTest()
    local frame = self.queueIndicatorTestFrame
    if frame then
        frame:Show()
        frame:Raise()
        return
    end

    local UI = addonTable.UI
    frame = CreateFrame("Frame", "WipedQoLQueueIndicatorTestFrame", UIParent, "BackdropTemplate")
    frame:SetSize(540, 154)
    frame:SetPoint("CENTER")
    frame:SetFrameStrata("DIALOG")
    frame:SetMovable(true)
    frame:EnableMouse(true)
    frame:RegisterForDrag("LeftButton")
    frame:SetScript("OnDragStart", frame.StartMoving)
    frame:SetScript("OnDragStop", frame.StopMovingOrSizing)
    if UI and UI.SkinFrame then
        UI.SkinFrame(frame)
    end

    local title = UI and UI.CreateLabel and UI.CreateLabel(frame, TRANSLATE("Indicator preview"), 14)
        or frame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
    title:SetText(TRANSLATE("Indicator preview"))
    title:SetTextColor(1, 1, 1)
    title:SetPoint("TOPLEFT", frame, "TOPLEFT", 14, -13)

    local closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
    closeButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -5, -5)
    closeButton:SetScript("OnClick", function()
        frame:Hide()
    end)
    if UI and UI.SkinCloseButton then
        UI.SkinCloseButton(closeButton)
    end

    local states = {
        { key = "waiting", label = "Waiting" },
        { key = "applicant", label = "New applicant" },
        { key = "ready", label = "Queue ready" },
        { key = "scenario", label = "Activity running" },
    }
    for index, stateInfo in ipairs(states) do
        local card = CreateFrame("Frame", nil, frame, "BackdropTemplate")
        card:SetSize(120, 91)
        card:SetPoint("TOPLEFT", frame, "TOPLEFT", 15 + ((index - 1) * 130), -48)
        if UI and UI.SkinFrame then
            UI.SkinFrame(card, { light = true })
        end

        local label = UI and UI.CreateLabel and UI.CreateLabel(card, TRANSLATE(stateInfo.label), 11)
            or card:CreateFontString(nil, "ARTWORK", "GameFontNormal")
        label:SetText(TRANSLATE(stateInfo.label))
        label:SetTextColor(0.82, 0.82, 0.82)
        label:SetPoint("TOP", card, "TOP", 0, -9)

        local indicator = CreateQueueIndicatorPreview(card, stateInfo.key)
        indicator:SetPoint("CENTER", card, "CENTER", 0, -11)
        indicator:SetScale(INDICATOR_BASE_SCALE)
    end

    if UISpecialFrames then
        table.insert(UISpecialFrames, "WipedQoLQueueIndicatorTestFrame")
    end
    self.queueIndicatorTestFrame = frame
end

local function CreateQueueDisplay(button)
    if button._wqolQueueDisplay then
        return button._wqolQueueDisplay
    end

    if button.SetClipsChildren then
        button:SetClipsChildren(false)
    end

    local holder = CreateFrame("Frame", nil, button)
    holder:SetSize(INDICATOR_HOLDER_SIZE, INDICATOR_HOLDER_SIZE)
    holder:SetPoint("CENTER", button, "CENTER", 0, 0)
    holder:SetFrameLevel(button:GetFrameLevel() + 49)
    if holder.SetClipsChildren then
        holder:SetClipsChildren(false)
    end

    local display = CreateFrame("Frame", nil, holder)
    display:SetSize(INDICATOR_ART_SIZE, INDICATOR_ART_SIZE)
    display:SetPoint("CENTER")
    display:SetFrameLevel(button:GetFrameLevel() + 50)
    if display.SetIgnoreParentAlpha then
        display:SetIgnoreParentAlpha(true)
    end

    display.glowEdges = {
        top = display:CreateTexture(nil, "BORDER"),
        bottom = display:CreateTexture(nil, "BORDER"),
        left = display:CreateTexture(nil, "BORDER"),
        right = display:CreateTexture(nil, "BORDER"),
    }
    display.glowEdges.top:SetSize(28, 2)
    display.glowEdges.top:SetPoint("CENTER", display, "CENTER", 0, 13)
    display.glowEdges.bottom:SetSize(28, 2)
    display.glowEdges.bottom:SetPoint("CENTER", display, "CENTER", 0, -13)
    display.glowEdges.left:SetSize(2, 28)
    display.glowEdges.left:SetPoint("CENTER", display, "CENTER", -13, 0)
    display.glowEdges.right:SetSize(2, 28)
    display.glowEdges.right:SetPoint("CENTER", display, "CENTER", 13, 0)
    for _, edge in pairs(display.glowEdges) do
        edge:SetTexture(WHITE_TEXTURE)
        edge:SetBlendMode("ADD")
        edge:SetAlpha(0)
    end

    local background = display:CreateTexture(nil, "BACKGROUND")
    background:SetPoint("TOPLEFT", 3, -3)
    background:SetPoint("BOTTOMRIGHT", -3, 3)
    background:SetColorTexture(0, 0, 0, 1)
    display.background = background

    display.people = {}
    for index = 1, 3 do
        local person = {}
        local layer = 6 - index

        local head = display:CreateTexture(nil, "ARTWORK", nil, layer)
        head:SetTexture(EYE_TEXTURE)
        head:SetSize(6, 6)
        person.head = head

        local shoulders = display:CreateTexture(nil, "ARTWORK", nil, layer)
        shoulders:SetTexture(EYE_TEXTURE)
        shoulders:SetSize(10, 6)
        person.shoulders = shoulders

        display.people[index] = person
    end

    local scenarioLabel = CreateFrame("Frame", nil, display)
    scenarioLabel:SetSize(16, 16)
    scenarioLabel:SetPoint("CENTER", 1, 0)
    scenarioLabel:SetFrameLevel(display:GetFrameLevel() + 2)
    local scenarioIcon = scenarioLabel:CreateTexture(nil, "OVERLAY")
    scenarioIcon:SetAllPoints()
    scenarioIcon:SetTexture(PLAY_TEXTURE)
    scenarioIcon:SetVertexColor(0.3, 1, 0.42, 1)
    scenarioLabel:Hide()
    display.scenarioLabel = scenarioLabel

    display.elapsed = 0
    display.updateElapsed = 0
    display.scenarioCheckElapsed = SCENARIO_CHECK_INTERVAL
    display.scenarioActiveCached = false
    display.applicantPulseElapsed = 0

    if hooksecurefunc and button.Eye and button.Eye.SetStaticMode then
        hooksecurefunc(button.Eye, "SetStaticMode", function(eye, isStatic)
            if display.enabled and not isStatic and not display.applyingStaticEye then
                display.applyingStaticEye = true
                eye:SetStaticMode(true)
                eye:SetAlpha(0)
                display.applyingStaticEye = nil
            end
        end)
    end
    if hooksecurefunc and button.UpdateGlow then
        hooksecurefunc(button, "UpdateGlow", function()
            if display.enabled and button.Highlight then
                button.Highlight:Hide()
            end
        end)
    end

    display:SetScript("OnUpdate", function(self, delta)
        if not self.enabled then
            return
        end

        self.updateElapsed = self.updateElapsed + delta
        if self.updateElapsed < UPDATE_INTERVAL then
            return
        end
        delta = self.updateElapsed
        self.updateElapsed = 0
        self.elapsed = self.elapsed + delta

        self.scenarioCheckElapsed = self.scenarioCheckElapsed + delta
        if self.scenarioCheckElapsed >= SCENARIO_CHECK_INTERVAL then
            self.scenarioCheckElapsed = 0
            self.scenarioActiveCached = IsActivityInProgress()
        end

        local applicantLock = button.glowLocks and button.glowLocks["lfglist-applicant"]
        local applicantAlert = not self.ready and applicantLock ~= nil and applicantLock ~= false
        local scenarioActive = not self.ready and self.scenarioActiveCached
        if self.lastReady ~= self.ready then
            self.lastReady = self.ready
            if self.ready then
                self.applicantPulsePlaying = nil
            end
        end

        if self.pendingApplicantPulse then
            self.pendingApplicantPulse = nil
            if applicantAlert then
                self.applicantPulseElapsed = 0
                self.applicantPulsePlaying = true
            end
        end

        local pulse = (math.sin(self.elapsed * STATUS_PULSE_SPEED) + 1) * 0.5
        local applicantPulse = 0
        if self.applicantPulsePlaying then
            self.applicantPulseElapsed = self.applicantPulseElapsed + delta
            local cycle = self.applicantPulseElapsed / APPLICANT_PULSE_DURATION
            if cycle >= APPLICANT_PULSE_COUNT then
                self.applicantPulsePlaying = nil
            else
                applicantPulse = math.sin(math.pi * (cycle % 1))
            end
        end

        if self.ready then
            self.scenarioLabel:Hide()
            for _, edge in pairs(self.glowEdges) do
                edge:SetAlpha(0.25 + pulse * 0.55)
            end
            self.background:SetColorTexture(0, 0, 0, 1)
            if not self.readyLayout then
                self.readyLayout = true
                for index, person in ipairs(self.people) do
                    person.head:ClearAllPoints()
                    person.shoulders:ClearAllPoints()
                    if index == 2 then
                        person.head:SetPoint("CENTER", self, "CENTER", 0, 4)
                        person.shoulders:SetPoint("CENTER", self, "CENTER", 0, -4)
                    end
                end
            end

            for index, person in ipairs(self.people) do
                local alpha = index == 2 and (0.68 + pulse * 0.32) or 0
                person.head:SetAlpha(alpha)
                person.shoulders:SetAlpha(alpha * 0.92)
                if index == 2 then
                    person.head:SetSize(6.5 + pulse * 1.5, 6.5 + pulse * 1.5)
                    person.shoulders:SetSize(10.5 + pulse * 2, 6.5 + pulse * 1.5)
                end
            end
        elseif scenarioActive then
            -- Ready-state events can recolor the shared border while a scenario
            -- is already running, so keep the scenario glow explicitly green.
            for _, edge in pairs(self.glowEdges) do
                edge:SetVertexColor(0.3, 1, 0.42)
            end
            self.scenarioActive = true
            self.scenarioLabel:Show()
            self.scenarioLabel:SetAlpha(0.55 + pulse * 0.45)
            self.scenarioLabel:SetScale(0.9 + pulse * 0.15)
            for _, edge in pairs(self.glowEdges) do
                edge:SetAlpha(0.8)
            end
            for _, person in ipairs(self.people) do
                person.head:SetAlpha(0)
                person.shoulders:SetAlpha(0)
            end
            self.background:SetColorTexture(0, 0, 0, 1)
        else
            self.scenarioLabel:Hide()
            self.scenarioLabel:SetScale(1)
            if self.scenarioActive then
                self.scenarioActive = nil
                self.visibleCount = nil
                for _, edge in pairs(self.glowEdges) do
                    edge:SetVertexColor(1, 0.72, 0.08)
                end
            end
            for _, edge in pairs(self.glowEdges) do
                edge:SetAlpha(applicantPulse * 0.8)
            end
            self.background:SetColorTexture(0, 0, 0, 1)
            if self.readyLayout then
                self.readyLayout = nil
                self.visibleCount = nil
            end

            local visibleCount = (math.floor(self.elapsed / STEP_DURATION) % 3) + 1
            local transition = (self.elapsed / STEP_DURATION) % 1
            if self.visibleCount ~= visibleCount then
                self.visibleCount = visibleCount
                local spread = visibleCount == 1 and 0 or (visibleCount == 2 and 4 or 7)
                local step = visibleCount > 1 and (spread * 2 / (visibleCount - 1)) or 0
                for index, person in ipairs(self.people) do
                    local x = spread - ((index - 1) * step)
                    local y = -spread * 0.55 + ((index - 1) * step * 0.55)
                    person.head:ClearAllPoints()
                    person.head:SetPoint("CENTER", self, "CENTER", x, y + 4)
                    person.shoulders:ClearAllPoints()
                    person.shoulders:SetPoint("CENTER", self, "CENTER", x, y - 4)
                end
            end

            for index, person in ipairs(self.people) do
                local isNewest = index == visibleCount
                local targetAlpha = 1 - ((index - 1) * 0.2)
                local alpha = index <= visibleCount and targetAlpha or 0
                if isNewest then
                    alpha = targetAlpha * (0.45 + transition * 0.55)
                end

                person.head:SetAlpha(alpha)
                person.shoulders:SetAlpha(alpha * 0.9)
                person.head:SetSize(isNewest and 6.5 or 6, isNewest and 6.5 or 6)
                person.shoulders:SetSize(isNewest and 10.5 or 10, isNewest and 6.5 or 6)
            end
        end
    end)

    button:HookScript("OnShow", function()
        if display.enabled then
            display.elapsed = 0
            display.updateElapsed = 0
            display.scenarioCheckElapsed = SCENARIO_CHECK_INTERVAL
            display:Show()
            SuppressBlizzardQueueArtwork(button)
        end
    end)
    button:HookScript("OnEnter", function()
        if display.enabled then
            display.background:SetColorTexture(0, 0, 0, 1)
        end
    end)
    button:HookScript("OnLeave", function()
        display.background:SetColorTexture(0, 0, 0, 1)
    end)

    button._wqolQueueHolder = holder
    button._wqolQueueDisplay = display
    SetIndicatorColor(display, false)
    return display
end

local function GetEllesmereQueueButtonOrder(profile)
    local rowPosition = profile and profile.btnRowPosition or "blUp"
    if rowPosition == "tlDown" or rowPosition == "trDown" then
        return ELLESMERE_FIRST_ORDER
    end
    return -ELLESMERE_FIRST_ORDER
end

local function SetEllesmereQueueButtonOrder(profile, enabled)
    if not profile then
        return
    end

    profile.ungroupedButtons = profile.ungroupedButtons or {}
    if enabled then
        profile.ungroupedButtons[ELLESMERE_QUEUE_BUTTON_NAME] = GetEllesmereQueueButtonOrder(profile)
    else
        profile.ungroupedButtons[ELLESMERE_QUEUE_BUTTON_NAME] = nil
    end
end

local ELLESMERE_TOOLTIP_ANCHORS = {
    left = { "TOPRIGHT", "TOPLEFT", -4, 0 },
    right = { "TOPLEFT", "TOPRIGHT", 4, 0 },
    up = { "BOTTOMLEFT", "TOPLEFT", 0, 4 },
    down = { "TOPLEFT", "BOTTOMLEFT", 0, -4 },
}

local function ReanchorEllesmereQueueTooltip(ellesmereButton)
    local tooltip = _G.GameTooltip
    if not tooltip or not tooltip:IsShown() then
        return
    end
    local owner = tooltip:GetOwner()

    local profile = GetEllesmereMinimapProfile()
    local direction = profile and profile.flyoutGrowDir or "auto"
    if direction == "auto" then
        local rowPosition = profile and profile.btnRowPosition or "blUp"
        local vertical = rowPosition == "blUp"
            or rowPosition == "tlDown"
            or rowPosition == "brUp"
            or rowPosition == "trDown"
        direction = vertical and "left" or "down"
    end

    local anchor = ELLESMERE_TOOLTIP_ANCHORS[direction] or ELLESMERE_TOOLTIP_ANCHORS.left
    if owner ~= ellesmereButton then
        tooltip:SetOwner(ellesmereButton, "ANCHOR_NONE")
    end
    tooltip:ClearAllPoints()
    tooltip:SetPoint(anchor[1], ellesmereButton, anchor[2], anchor[3], anchor[4])
    tooltip:Show()
end

local function CreateEllesmereQueueButton()
    local button = _G[ELLESMERE_QUEUE_BUTTON_NAME]
    if not button then
        button = CreateFrame("Button", ELLESMERE_QUEUE_BUTTON_NAME, _G.Minimap)
        button:SetSize(24, 24)
    end

    button:EnableMouse(true)
    button:RegisterForClicks("AnyUp")
    button:Hide()
    button:SetScript("OnClick", function(_, mouseButton)
        local queueButton = _G.QueueStatusButton
        local onClick = queueButton and queueButton:GetScript("OnClick")
        if onClick then
            onClick(queueButton, mouseButton)
        end
    end)
    button:SetScript("OnEnter", function(self)
        local queueButton = _G.QueueStatusButton
        local onEnter = queueButton and queueButton:GetScript("OnEnter")
        if onEnter then
            onEnter(queueButton)
        end
        ReanchorEllesmereQueueTooltip(self)
        C_Timer.After(0, function()
            if self:IsMouseOver() then
                ReanchorEllesmereQueueTooltip(self)
            end
        end)
    end)
    button:SetScript("OnLeave", function(self)
        local queueButton = _G.QueueStatusButton
        local onLeave = queueButton and queueButton:GetScript("OnLeave")
        if onLeave then
            onLeave(queueButton)
        end
        local tooltip = _G.GameTooltip
        if tooltip and tooltip:GetOwner() == self then
            tooltip:Hide()
        end
    end)
    button:SetScript("OnSizeChanged", function()
        if WipedQoL.ellesmereQueueIndicatorIntegrated then
            WipedQoL:ApplyQueueIndicatorLayout()
        end
    end)
    local tooltip = _G.GameTooltip
    if tooltip and not tooltip._wqolEllesmereQueuePositionHooked then
        tooltip:HookScript("OnUpdate", function()
            local proxy = WipedQoL.ellesmereQueueIndicatorButton
            if WipedQoL.ellesmereQueueIndicatorIntegrated
                and proxy
                and proxy:IsShown()
                and proxy:IsMouseOver()
            then
                ReanchorEllesmereQueueTooltip(proxy)
            end
        end)
        tooltip._wqolEllesmereQueuePositionHooked = true
    end
    return button
end

local function SyncEllesmereQueueButtonVisibility(queueButton, ellesmereButton)
    if not queueButton or not ellesmereButton then
        return
    end

    local shown = queueButton:IsShown()
    ellesmereButton:SetShown(shown)
    local visibility = _G._EBS_AddonVisible
    if visibility then
        visibility[ellesmereButton] = shown
    end
end

local ELLESMERE_VERTICAL_ROW_ANCHORS = {
    blUp = { "BOTTOMRIGHT", "BOTTOMLEFT" },
    tlDown = { "TOPRIGHT", "TOPLEFT" },
    brUp = { "BOTTOMLEFT", "BOTTOMRIGHT" },
    trDown = { "TOPLEFT", "TOPRIGHT" },
}

local function MoveEllesmereQueueButtonToVisualBottom(ellesmereButton, profile)
    local minimap = _G.Minimap
    local rowAnchors = ELLESMERE_VERTICAL_ROW_ANCHORS[profile and profile.btnRowPosition or "blUp"]
    if not minimap or not ellesmereButton or not ellesmereButton:IsShown() or not rowAnchors then
        return
    end

    local buttonBottom = ellesmereButton:GetBottom()
    if not buttonBottom then
        return
    end

    local bottomButton
    local bottomEdge = buttonBottom
    for _, child in ipairs({ minimap:GetChildren() }) do
        if child ~= ellesmereButton
            and child.IsObjectType
            and child:IsObjectType("Button")
            and child:IsShown()
            and child:GetNumPoints() == 1
        then
            local point, relativeTo, relativePoint = child:GetPoint(1)
            local childBottom = child:GetBottom()
            if point == rowAnchors[1]
                and relativeTo == minimap
                and relativePoint == rowAnchors[2]
                and childBottom
                and childBottom < bottomEdge
            then
                bottomButton = child
                bottomEdge = childBottom
            end
        end
    end

    if not bottomButton then
        return
    end

    local buttonPoint, buttonRelativeTo, buttonRelativePoint, buttonX, buttonY = ellesmereButton:GetPoint(1)
    local bottomPoint, bottomRelativeTo, bottomRelativePoint, bottomX, bottomY = bottomButton:GetPoint(1)
    if not buttonPoint or not bottomPoint then
        return
    end

    ellesmereButton:ClearAllPoints()
    bottomButton:ClearAllPoints()
    ellesmereButton:SetPoint(bottomPoint, bottomRelativeTo, bottomRelativePoint, bottomX, bottomY)
    bottomButton:SetPoint(buttonPoint, buttonRelativeTo, buttonRelativePoint, buttonX, buttonY)
end

function WipedQoL:RequestEllesmereQueueButtonBottomPosition()
    if not self.ellesmereQueueIndicatorIntegrated
        or not self.ellesmereQueueIndicatorButton
        or self.ellesmereQueueIndicatorBottomPositionPending
    then
        return
    end
    if InCombatLockdown and InCombatLockdown() then
        self:DeferEllesmereQueueIndicatorIntegration()
        return
    end

    self.ellesmereQueueIndicatorBottomPositionPending = true
    C_Timer.After(0, function()
        if not WipedQoL.ellesmereQueueIndicatorIntegrated then
            WipedQoL.ellesmereQueueIndicatorBottomPositionPending = nil
            return
        end
        local profile = GetEllesmereMinimapProfile()
        local proxy = WipedQoL.ellesmereQueueIndicatorButton
        MoveEllesmereQueueButtonToVisualBottom(proxy, profile)
        WipedQoL:ApplyQueueIndicatorLayout()
        WipedQoL.ellesmereQueueIndicatorBottomPositionPending = nil
    end)
end

function WipedQoL:IsEllesmereMinimapAvailable()
    return GetEllesmereMinimapProfile() ~= nil
        and _G.Minimap ~= nil
        and type(_G._EMM_ApplyMinimap) == "function"
end

function WipedQoL:IsEllesmereMinimapActive()
    local profile = GetEllesmereMinimapProfile()
    return profile ~= nil
        and profile.enabled ~= false
        and _G.Minimap ~= nil
        and type(_G._EMM_ApplyMinimap) == "function"
end

function WipedQoL:IsEllesmereQueueIndicatorEnabled()
    local settings = self:GetQueueIndicatorSettings()
    return settings and settings.ellesmereMinimap == true
end

function WipedQoL:DeferEllesmereQueueIndicatorIntegration()
    if not self.ellesmereQueueIndicatorCombatFrame then
        local frame = CreateFrame("Frame")
        frame:SetScript("OnEvent", function(self)
            self:UnregisterEvent("PLAYER_REGEN_ENABLED")
            WipedQoL:ApplyEllesmereQueueIndicatorIntegration()
        end)
        self.ellesmereQueueIndicatorCombatFrame = frame
    end
    self.ellesmereQueueIndicatorCombatFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
end

function WipedQoL:RequestEllesmereMinimapLayout()
    if InCombatLockdown and InCombatLockdown() then
        self:DeferEllesmereQueueIndicatorIntegration()
        return
    end
    if self.ellesmereQueueIndicatorLayoutPending then
        return
    end

    self.ellesmereQueueIndicatorLayoutPending = true
    C_Timer.After(0, function()
        WipedQoL.ellesmereQueueIndicatorLayoutPending = nil

        local profile = GetEllesmereMinimapProfile()
        local queueButton = _G.QueueStatusButton
        local ellesmereButton = WipedQoL.ellesmereQueueIndicatorButton
        if WipedQoL.ellesmereQueueIndicatorIntegrated and ellesmereButton then
            SetEllesmereQueueButtonOrder(profile, true)
            SyncEllesmereQueueButtonVisibility(queueButton, ellesmereButton)
        end

        if type(_G._EMM_ApplyMinimap) == "function" then
            _G._EMM_ApplyMinimap()
        end
        if WipedQoL.ellesmereQueueIndicatorIntegrated and ellesmereButton then
            MoveEllesmereQueueButtonToVisualBottom(ellesmereButton, profile)
        end
        WipedQoL:ApplyQueueIndicatorLayout()
    end)
end

function WipedQoL:ApplyEllesmereQueueIndicatorIntegration()
    local queueButton = _G.QueueStatusButton
    local settings = self:GetQueueIndicatorSettings()
    if not queueButton or not settings then
        return false
    end

    local shouldIntegrate = settings.enabled == true
        and settings.ellesmereMinimap == true
        and self:IsEllesmereMinimapActive()

    if InCombatLockdown and InCombatLockdown() then
        self:DeferEllesmereQueueIndicatorIntegration()
        return false
    end

    local profile = GetEllesmereMinimapProfile()
    if shouldIntegrate then
        local ellesmereButton = self.ellesmereQueueIndicatorButton or CreateEllesmereQueueButton()
        self.ellesmereQueueIndicatorButton = ellesmereButton

        SetEllesmereQueueButtonOrder(profile, true)
        self.ellesmereQueueIndicatorIntegrated = true
        ellesmereButton:SetShown(queueButton:IsShown())
        SyncEllesmereQueueButtonVisibility(queueButton, ellesmereButton)

        if not ellesmereButton._wqolEllesmerePositionHooked then
            hooksecurefunc(ellesmereButton, "SetPoint", function()
                WipedQoL:RequestEllesmereQueueButtonBottomPosition()
            end)
            ellesmereButton._wqolEllesmerePositionHooked = true
        end

        if not self.ellesmereQueueIndicatorLayoutHooked then
            hooksecurefunc("_EMM_ApplyMinimap", function()
                if not WipedQoL.ellesmereQueueIndicatorIntegrated then
                    return
                end
                WipedQoL:RequestEllesmereQueueButtonBottomPosition()
            end)
            self.ellesmereQueueIndicatorLayoutHooked = true
        end

        if not queueButton._wqolEllesmereVisibilityHooked then
            hooksecurefunc(queueButton, "Show", function()
                if not WipedQoL.ellesmereQueueIndicatorIntegrated then
                    return
                end
                local proxy = WipedQoL.ellesmereQueueIndicatorButton
                if proxy then
                    proxy:Show()
                    local visibility = _G._EBS_AddonVisible
                    if visibility then
                        visibility[proxy] = true
                    end
                    WipedQoL:RequestEllesmereMinimapLayout()
                end
            end)
            hooksecurefunc(queueButton, "Hide", function()
                if not WipedQoL.ellesmereQueueIndicatorIntegrated then
                    return
                end
                local proxy = WipedQoL.ellesmereQueueIndicatorButton
                if proxy then
                    proxy:Hide()
                    local visibility = _G._EBS_AddonVisible
                    if visibility then
                        visibility[proxy] = false
                    end
                    WipedQoL:RequestEllesmereMinimapLayout()
                end
            end)
            queueButton._wqolEllesmereVisibilityHooked = true
        end

        self:ApplyQueueIndicatorLayout()
        self:RequestEllesmereMinimapLayout()
        return true
    end

    SetEllesmereQueueButtonOrder(profile, false)
    if self.ellesmereQueueIndicatorIntegrated then
        self.ellesmereQueueIndicatorIntegrated = nil
        if self.ellesmereQueueIndicatorButton then
            self.ellesmereQueueIndicatorButton:Hide()
            local visibility = _G._EBS_AddonVisible
            if visibility then
                visibility[self.ellesmereQueueIndicatorButton] = false
            end
        end
        self:ApplyQueueIndicatorLayout()
        self:RequestEllesmereMinimapLayout()
    end
    return false
end

function WipedQoL:SetEllesmereQueueIndicatorEnabled(enabled)
    local settings = self:GetQueueIndicatorSettings()
    if not settings or (enabled and not self:IsEllesmereMinimapAvailable()) then
        return false
    end

    settings.ellesmereMinimap = enabled == true
    self:ApplyEllesmereQueueIndicatorIntegration()
    if self.RefreshQueueIndicatorSkinningUI then
        self:RefreshQueueIndicatorSkinningUI()
    end
    return true
end

function WipedQoL:GetQueueIndicatorSettings()
    local modules = self.db and self.db.profile and self.db.profile.modules
    if not modules then
        return nil
    end

    modules.queueIndicator = modules.queueIndicator or {}
    if modules.queueIndicator.enabled == nil then
        modules.queueIndicator.enabled = true
    end
    if modules.queueIndicator.ellesmereMinimap == nil then
        modules.queueIndicator.ellesmereMinimap = false
    end
    modules.queueIndicator.scale = ClampScale(modules.queueIndicator.scale)
    modules.queueIndicator.position = nil
    if not VALID_DOCKS[modules.queueIndicator.dock] then
        modules.queueIndicator.dock = "CENTER"
    end
    return modules.queueIndicator
end

function WipedQoL:IsQueueIndicatorEnabled()
    local settings = self:GetQueueIndicatorSettings()
    return settings and settings.enabled == true
end

function WipedQoL:GetQueueIndicatorScale()
    local settings = self:GetQueueIndicatorSettings()
    return ClampScale(settings and settings.scale)
end

function WipedQoL:GetQueueIndicatorDock()
    local settings = self:GetQueueIndicatorSettings()
    local dock = settings and settings.dock or "CENTER"
    return VALID_DOCKS[dock] and dock or "CENTER"
end

function WipedQoL:ApplyQueueIndicatorLayout()
    local button = _G.QueueStatusButton
    local display = button and button._wqolQueueDisplay
    local holder = button and button._wqolQueueHolder
    if not display or not holder then
        return false
    end

    local ellesmereButton = self.ellesmereQueueIndicatorIntegrated and self.ellesmereQueueIndicatorButton
    local holderParent = ellesmereButton or button
    if holder:GetParent() ~= holderParent then
        holder:SetParent(holderParent)
    end
    holder:ClearAllPoints()
    holder:SetPoint("CENTER", holderParent, "CENTER", 0, 0)
    holder:SetSize(INDICATOR_HOLDER_SIZE, INDICATOR_HOLDER_SIZE)
    holder:SetFrameStrata(holderParent:GetFrameStrata())
    holder:SetFrameLevel(holderParent:GetFrameLevel() + 2)
    display:SetFrameLevel(holder:GetFrameLevel() + 1)
    if display.scenarioLabel then
        display.scenarioLabel:SetFrameLevel(display:GetFrameLevel() + 2)
    end
    display:ClearAllPoints()
    if ellesmereButton then
        local size = math.min(
            ellesmereButton:GetWidth() or 24,
            ellesmereButton:GetHeight() or 24
        )
        size = math.max(size, 1)
        display:SetPoint("CENTER", holder, "CENTER", 0, 0)
        -- The artwork has three pixels of transparent padding on every side.
        -- Scale its visible 28px area, not the padded 34px frame, to Ellesmere's slot.
        display:SetScale((size / INDICATOR_VISIBLE_SIZE) * self:GetQueueIndicatorScale())
    else
        local dock = self:GetQueueIndicatorDock()
        display:SetPoint(dock, holder, dock, 0, 0)
        display:SetScale(INDICATOR_BASE_SCALE * self:GetQueueIndicatorScale())
    end
    return true
end

function WipedQoL:SetQueueIndicatorScale(scale)
    local settings = self:GetQueueIndicatorSettings()
    if not settings then
        return false
    end

    settings.scale = ClampScale(scale)
    self:ApplyQueueIndicatorLayout()
    if self.RefreshQueueIndicatorSkinningUI then
        self:RefreshQueueIndicatorSkinningUI()
    end
    return true
end

function WipedQoL:SetQueueIndicatorDock(dock)
    local settings = self:GetQueueIndicatorSettings()
    if not settings or not VALID_DOCKS[dock] then
        return false
    end

    settings.dock = dock
    self:ApplyQueueIndicatorLayout()
    if self.RefreshQueueIndicatorSkinningUI then
        self:RefreshQueueIndicatorSkinningUI()
    end
    return true
end

function WipedQoL:ResetQueueIndicator()
    local settings = self:GetQueueIndicatorSettings()
    settings.scale = 1
    settings.dock = "CENTER"
    self:ApplyQueueIndicatorLayout()
    if self.RefreshQueueIndicatorSkinningUI then
        self:RefreshQueueIndicatorSkinningUI()
    end
    return true
end

function WipedQoL:ApplyQueueIndicatorState()
    local button = _G.QueueStatusButton
    if not button then
        return
    end

    local display = CreateQueueDisplay(button)
    display.enabled = self:IsQueueIndicatorEnabled()
    display:SetShown(display.enabled)
    if display.enabled then
        self:ApplyQueueIndicatorLayout()
        SuppressBlizzardQueueArtwork(button)
    else
        RestoreBlizzardQueueArtwork(button)
    end
    self:ApplyEllesmereQueueIndicatorIntegration()
end

function WipedQoL:SetQueueIndicatorEnabled(enabled)
    local settings = self:GetQueueIndicatorSettings()
    if not settings then
        return false
    end

    settings.enabled = enabled == true
    self:ApplyQueueIndicatorState()
    if self.RefreshQueueIndicatorSkinningUI then
        self:RefreshQueueIndicatorSkinningUI()
    end
    return true
end

function WipedQoL:RefreshQueueIndicatorReadyState()
    local button = _G.QueueStatusButton
    local display = button and button._wqolQueueDisplay
    if not display then
        return
    end

    local readyDialog = _G.LFGDungeonReadyDialog
    local ready = readyDialog and readyDialog.IsVisible and readyDialog:IsVisible()
    SetIndicatorColor(display, ready)
end

function WipedQoL:SetupQueueIndicator()
    local button = _G.QueueStatusButton
    if not button then
        return false
    end

    CreateQueueDisplay(button)
    self:ApplyQueueIndicatorState()
    self:RefreshQueueIndicatorReadyState()
    return true
end

function WipedQoL:InitializeQueueIndicator()
    if self:SetupQueueIndicator() then
        return
    end

    local loader = CreateFrame("Frame")
    loader:RegisterEvent("ADDON_LOADED")
    loader:RegisterEvent("PLAYER_ENTERING_WORLD")
    loader:SetScript("OnEvent", function(self)
        C_Timer.After(0, function()
            if WipedQoL:SetupQueueIndicator() then
                self:UnregisterAllEvents()
            end
        end)
    end)
    self.queueIndicatorLoader = loader
end

local readyEvents = CreateFrame("Frame")
readyEvents:RegisterEvent("LFG_PROPOSAL_SHOW")
readyEvents:RegisterEvent("LFG_PROPOSAL_UPDATE")
readyEvents:RegisterEvent("LFG_PROPOSAL_FAILED")
readyEvents:RegisterEvent("LFG_PROPOSAL_SUCCEEDED")
readyEvents:SetScript("OnEvent", function(_, event)
    C_Timer.After(event == "LFG_PROPOSAL_SHOW" and 0 or 0.1, function()
        WipedQoL:RefreshQueueIndicatorReadyState()
    end)
end)

local applicantEvents = CreateFrame("Frame")
applicantEvents:RegisterEvent("LFG_LIST_APPLICANT_LIST_UPDATED")
applicantEvents:SetScript("OnEvent", function(_, _, hasNewPending, hasNewPendingWithData)
    if not hasNewPending or not hasNewPendingWithData then
        return
    end

    local button = _G.QueueStatusButton
    local display = button and button._wqolQueueDisplay
    if display and display.enabled then
        display.pendingApplicantPulse = true
    end
end)

local ellesmereIntegrationEvents = CreateFrame("Frame")
ellesmereIntegrationEvents:RegisterEvent("ADDON_LOADED")
ellesmereIntegrationEvents:RegisterEvent("PLAYER_ENTERING_WORLD")
ellesmereIntegrationEvents:SetScript("OnEvent", function(_, event, addonName)
    if event == "ADDON_LOADED" and addonName ~= ELLESMERE_MINIMAP_ADDON then
        return
    end

    C_Timer.After(0.1, function()
        if _G.QueueStatusButton then
            WipedQoL:ApplyEllesmereQueueIndicatorIntegration()
        end
    end)
end)
