setfenv(1, VoiceOver)
SoundQueueUI = {}
SoundQueueUI.__index = SoundQueueUI

local LibDataBroker = LibStub("LibDataBroker-1.1")
local LibDBIcon = LibStub("LibDBIcon-1.0")

local PORTRAIT_SIZE = 120
local PORTRAIT_ATLAS_SIZE = 512
local PORTRAIT_ATLAS_BORDER_SIZE = 416
local PORTRAIT_ATLAS_VIEWPORT_SIZE = 348
local PORTRAIT_BORDER_SCALE = PORTRAIT_SIZE / PORTRAIT_ATLAS_VIEWPORT_SIZE
local PORTRAIT_BORDER_SIZE = PORTRAIT_ATLAS_BORDER_SIZE * PORTRAIT_BORDER_SCALE
local PORTRAIT_BORDER_OUTSET = 34 * PORTRAIT_BORDER_SCALE
local PORTRAIT_LINE_WIDTH = 56 * PORTRAIT_BORDER_SCALE
local FRAME_WIDTH_WITHOUT_PORTRAIT = 300
local HIDE_GOSSIP_OPTIONS = true -- Disable to allow gossip options to show up in the button list, like quests
local WAIT_FOR_ANIMATION_FINISH_BEFORE_IDLE = true

do
    local font = CreateFont("VoiceOverNameFont")
    font:SetFont(GameFontNormal:GetFont(), 19, "")
    font:SetShadowColor(0, 0, 0)
    font:SetShadowOffset(1, -1)
    font:SetJustifyH("LEFT")
    font:SetJustifyV("TOP")
end
do
    local font = CreateFont("VoiceOverButtonFont")
    font:SetFont(GameFontNormal:GetFont(), 16, "")
    font:SetShadowColor(0, 0, 0)
    font:SetShadowOffset(1, -1)
end

function SoundQueueUI:new(soundQueue)
    local self = {}
    setmetatable(self, SoundQueueUI)

    self.soundQueue = soundQueue

    self:InitDisplay()
    self:InitPortraitLine()
    self:InitPortrait()
    self:InitMover()
    self:InitMinimapButton()

    self:RefreshConfig()

    return self
end

function SoundQueueUI:InitDisplay()
    local soundQueueUI = self

    self.frame = CreateFrame("Frame", "VoiceOverFrame", UIParent)
    function self.frame:Reset()
        self:SetWidth(PORTRAIT_SIZE + FRAME_WIDTH_WITHOUT_PORTRAIT)
        self:SetHeight(PORTRAIT_SIZE)
        self:ClearAllPoints()
        self:SetPoint("BOTTOM", 0, 280)
    end
    self.frame:Reset()
    self.frame:SetMovable(true)         -- Allow the frame to be moved
    self.frame:SetResizable(true)       -- Allow the frame to be resized
    self.frame:SetClampedToScreen(true) -- Prevent from being dragged off-screen
    self.frame:SetUserPlaced(true)
    self.frame:SetFrameStrata(Addon.db.profile.SoundQueueUI.FrameStrata)

    -- Create a background gradient behind the queue container
    self.frame.background = self.frame:CreateTexture(nil, "BACKGROUND")
    self.frame.background:SetPoint("RIGHT")
    self.frame.background:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\BackgroundGradient]])

    -- Create a button to resize the main frame
    self.frame.resizer = CreateFrame("Button", nil, self.frame)
    self.frame.resizer:SetPoint("BOTTOMRIGHT")
    self.frame.resizer:SetSize(16, 16)
    self.frame.resizer:SetNormalTexture([[Interface\AddOns\AI_VoiceOver\Textures\SizeGrabber-Up]])
    self.frame.resizer:SetPushedTexture([[Interface\AddOns\AI_VoiceOver\Textures\SizeGrabber-Down]])
    self.frame.resizer:SetHighlightTexture([[Interface\AddOns\AI_VoiceOver\Textures\SizeGrabber-Highlight]])
    self.frame.resizer:HookScript("OnEnter", function() SetCursor([[Interface\Cursor\UI-Cursor-SizeRight]]) end)
    self.frame.resizer:HookScript("OnLeave", function() SetCursor(nil) end)
    self.frame.resizer:HookScript("OnMouseDown", function()
        self.frame.resizer:GetHighlightTexture():Hide()
        self.frame:StartSizing("BOTTOMRIGHT")
    end)
    self.frame.resizer:HookScript("OnMouseUp", function()
        self.frame.resizer:GetHighlightTexture():Show()
        self.frame:StopMovingOrSizing()
    end)

    -- Creature queue buttons container
    self.frame.container = CreateFrame("Frame", nil, self.frame)
    self.frame.container:SetPoint("RIGHT")
    self.frame.container.buttons = {}
    function self.frame.container.buttons:Update()
        for _, button in ipairs(self) do
            button:Update()
        end
    end

    -- Create NPC name text
    local SKIP_GOSSIP_BUTTON_OFFSET = -5
    self.frame.container.name = self.frame.container:CreateFontString(nil, "ARTWORK", "VoiceOverNameFont")
    self.frame.container.name:SetPoint("TOPLEFT")
    self.frame.container.name:SetWordWrap(false)
    self.frame.container.name:SetTextColor(214 / 255, 214 / 255, 214 / 255)
    function self.frame.container.name:Update()
        self:SetWidth(0)
        self:SetText(self:GetText())
        self:SetWidth(math.min(self:GetParent():GetWidth() - (soundQueueUI.frame.container.stopGossip:IsShown() and 32 + SKIP_GOSSIP_BUTTON_OFFSET or 0), self:GetStringWidth()))
    end

    -- Create Stop Gossip button
    self.frame.container.stopGossip = CreateFrame("Button", nil, self.frame.container)
    self.frame.container.stopGossip:SetSize(32, 32)
    self.frame.container.stopGossip:SetPoint("BOTTOMLEFT", self.frame.container.name, "RIGHT", SKIP_GOSSIP_BUTTON_OFFSET, 0)
    function self.frame.container.stopGossip:SetGossipCount(gossipCount)
        local texture = gossipCount > 1 and [[Interface\AddOns\AI_VoiceOver\Textures\StopGossipMore]] or [[Interface\AddOns\AI_VoiceOver\Textures\StopGossip]]
        self:SetShown(gossipCount > 0)
        self:EnableMouse(soundQueueUI.soundQueue:CanBePaused() or gossipCount > 1)
        self:SetHighlightTexture(texture, "ADD")
        self:SetNormalTexture(texture)
        self:SetPushedTexture(texture)

        self.tooltip = soundQueueUI.soundQueue:CanBePaused() and (gossipCount > 1 and "Next Gossip" or "Stop Gossip") or "Skip Remaining Gossips"
        if GameTooltip:GetOwner() == self then
            GameTooltip:SetText(self.tooltip)
            GameTooltip:Show()
        end
    end
    self.frame.container.stopGossip:SetGossipCount(0)
    self.frame.container.stopGossip:GetHighlightTexture():SetAlpha(0.5)
    self.frame.container.stopGossip:GetPushedTexture():SetAlpha(0.5)
    self.frame.container.stopGossip:HookScript("OnEnter", function(self)
        GameTooltip:SetOwner(self, "ANCHOR_NONE")
        GameTooltip:SetPoint("LEFT", self, "RIGHT")
        GameTooltip:SetText(self.tooltip)
        GameTooltip:Show()
    end)
    self.frame.container.stopGossip:HookScript("OnLeave", GameTooltip_Hide)
    self.frame.container.stopGossip:HookScript("OnClick", function()
        PlaySound(SOUNDKIT.CHAT_SCROLL_BUTTON)
        if soundQueueUI.soundQueue:CanBePaused() then
            -- Skip current
            local soundData = self.soundQueue.sounds[1]
            if soundData and Enums.SoundEvent:IsGossipEvent(soundData.event) then
                self.soundQueue:RemoveSoundFromQueue(soundData)
            end
        else
            -- Skip all remaining
            local soundData = self.soundQueue.sounds[2]
            while soundData and Enums.SoundEvent:IsGossipEvent(soundData.event) do
                self.soundQueue:RemoveSoundFromQueue(soundData)
                soundData = self.soundQueue.sounds[2]
            end
        end
    end)

    self.frame:HookScript("OnSizeChanged", function()
        self.frame.container.name:Update()
        self.frame.container.buttons:Update()
    end)
end

function SoundQueueUI:InitPortraitLine()
    -- Create a vertical line that will be visible instead of the portrait if the player turned the portrait off
    self.frame.portraitLine = self.frame:CreateTexture(nil, "BORDER")
    self.frame.portraitLine:SetPoint("TOPLEFT", -PORTRAIT_LINE_WIDTH / 2 + 2, PORTRAIT_BORDER_OUTSET)
    self.frame.portraitLine:SetPoint("BOTTOMRIGHT", self.frame, "BOTTOMLEFT", PORTRAIT_LINE_WIDTH / 2 + 2, -PORTRAIT_BORDER_OUTSET)
    self.frame.portraitLine:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameAtlas]])
    self.frame.portraitLine:SetTexCoord(456 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE, 0, PORTRAIT_ATLAS_BORDER_SIZE / PORTRAIT_ATLAS_SIZE)

    -- Create a play/pause button on the vertical line that will be visible if the player turned the portrait off
    self.frame.miniPause = CreateFrame("Button", nil, self.frame)
    self.frame.miniPause:SetSize(26, 26)
    --self.frame.miniPause:SetPoint("CENTER", self.frame.container.name, "LEFT", -20 + 2, 0) -- Use this to make the button be placed next to NPC name instead of always centered
    self.frame.miniPause:SetPoint("CENTER", self.frame.container, "LEFT", -20 + 2, 0)
    self.frame.miniPause:SetNormalTexture([[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameAtlas]])
    self.frame.miniPause:GetNormalTexture():ClearAllPoints()
    self.frame.miniPause:GetNormalTexture():SetPoint("CENTER")
    self.frame.miniPause:GetNormalTexture():SetSize(14, 14)
    self.frame.miniPause:SetPushedTexture([[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameAtlas]])
    self.frame.miniPause:GetPushedTexture():ClearAllPoints()
    self.frame.miniPause:GetPushedTexture():SetPoint("CENTER")
    self.frame.miniPause:GetPushedTexture():SetSize(12, 12)
    self.frame.miniPause.background = self.frame.miniPause:CreateTexture(nil, "BACKGROUND")
    self.frame.miniPause.background:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\SettingsButton]])
    self.frame.miniPause.background:SetPoint("CENTER")
    self.frame.miniPause.background:SetSize(32, 32)
    function self.frame.miniPause:Update()
        if Addon.db.char.IsPaused then
            self:GetNormalTexture():SetTexCoord(0 / PORTRAIT_ATLAS_SIZE, 93 / PORTRAIT_ATLAS_SIZE, 419 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE)
            self:GetPushedTexture():SetTexCoord(0 / PORTRAIT_ATLAS_SIZE, 93 / PORTRAIT_ATLAS_SIZE, 419 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE)
            self:GetNormalTexture():SetAlpha(MouseIsOver(self) and 1 or 0.75)
        else
            self:GetNormalTexture():SetTexCoord(93 / PORTRAIT_ATLAS_SIZE, 186 / PORTRAIT_ATLAS_SIZE, 419 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE)
            self:GetPushedTexture():SetTexCoord(93 / PORTRAIT_ATLAS_SIZE, 186 / PORTRAIT_ATLAS_SIZE, 419 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE)
            self:GetNormalTexture():SetAlpha(MouseIsOver(self) and 1 or 0.75)
        end
    end
    self.frame.miniPause:Update()
    self.frame.miniPause:HookScript("OnEnter", function(self)
        self:GetNormalTexture():SetAlpha(1)
    end)
    self.frame.miniPause:HookScript("OnLeave", function(self)
        self:GetNormalTexture():SetAlpha(0.75)
    end)
    self.frame.miniPause:HookScript("OnClick", function()
        PlaySound(SOUNDKIT.CHAT_SCROLL_BUTTON)
        self.soundQueue:TogglePauseQueue()
    end)
end

local function ShouldShowBookForGUID(guid)
    local type = guid and Utils:GetGUIDType(guid)
    if not type then
        return true -- A fallback in case the GUID is missing
    elseif type == Enums.GUID.Item then
        return true -- Maybe display the item icon in the future
    elseif type == Enums.GUID.GameObject then
        return true -- Maybe display the object model in the future
    end
    return false
end

function SoundQueueUI:InitPortrait()
    local soundQueueUI = self

    -- Create a container frame for the portrait
    self.frame.portrait = CreateFrame("Frame", nil, self.frame)
    self.frame.portrait:SetPoint("TOPLEFT")
    self.frame.portrait:SetSize(PORTRAIT_SIZE, PORTRAIT_SIZE)
    function self.frame.portrait:Configure(soundData)
        if not self:IsShown() then
            return
        end

        if not soundData then
            self.model:Hide()
            self.book:Hide()
        elseif ShouldShowBookForGUID(soundData.unitGUID) then
            self.model:Hide()
            self.book:Show()
        else
            self.model:Show()
            self.book:Hide()

            local creatureID = Utils:GetIDFromGUID(soundData.unitGUID)

            if creatureID ~= self.model.oldCreatureID then
                self.model:SetCreature(creatureID)
                self.model:SetCustomCamera(0)

                self.model.animation = 60
                self.model.animDuration = nil
                self.model.animDelay = soundData.delay
                self.model.animtimer = nil

                self.model.oldCreatureID = creatureID
            elseif self.model.wasPaused then
                self.model.animation = 60
                self.model.animDelay = soundData.delay
            end
        end
    end

    -- Create a background behind the model
    self.frame.portrait.background = self.frame.portrait:CreateTexture(nil, "BACKGROUND")
    self.frame.portrait.background:SetAllPoints()
    self.frame.portrait.background:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameBackground]])

    -- Create a 3D model
    self.frame.portrait.model = CreateFrame("DressUpModel", nil, self.frame.portrait)
    self.frame.portrait.model:SetAllPoints()
    self.frame.portrait.model:HookScript("OnHide", function(self)
        self:ClearModel()
        self.oldCreatureID = nil
        self.animation = nil
        self.animDuration = nil
        self.animDelay = nil
        self.animtimer = nil
    end)
    self.frame.portrait.model:HookScript("OnUpdate", function(self, elapsed)
        -- Refresh camera and animation in case the model wasn't loaded instantly
        self:SetCustomCamera(0)
        if self.animation and not self.animDuration then
            self.animDuration = Utils:GetModelAnimationDuration(self:GetModelFileID(), self.animation)
        end
        -- Wait out the delay, if any
        if self.animDelay and not Addon.db.char.IsPaused then
            self.animDelay = self.animDelay - elapsed
            if self.animDelay < 0 then
                self.animDelay = nil
            else
                return
            end
        end
        local isPaused = not soundQueueUI.soundQueue:IsPlaying()
        if not WAIT_FOR_ANIMATION_FINISH_BEFORE_IDLE and self.animation and self.animation ~= 0 and not self.animDelay and isPaused then
            -- Switch to idle animation as soon as the VO is paused
            self.animation = 0
            self.animtimer = nil
            self:SetAnimation(self.animation)
        end
        -- Loop the animation when the timer has reached the animation duration
        if self.animation and GetTime() - (self.animtimer or 0) >= (self.animDuration or 2) then
            if isPaused then
                if WAIT_FOR_ANIMATION_FINISH_BEFORE_IDLE and self.animation ~= 0 and not self.animDelay then
                    -- Switch to idle animation after the current animation is finished
                    self.animation = 0
                    self.animtimer = nil
                    self:SetAnimation(self.animation)
                end
            else
                -- Restart the current animation
                self.animtimer = GetTime()
                self:SetAnimation(self.animation)
            end
        end
        self.wasPaused = isPaused
    end)

    -- Create a book icon replacement when the 3D portrait is unavailable
    self.frame.portrait.book = self.frame.portrait:CreateTexture(nil, "ARTWORK")
    self.frame.portrait.book:SetAllPoints()
    self.frame.portrait.book:SetTexture([[Interface\Icons\INV_Misc_Book_09]])
    self.frame.portrait.book:Hide()

    -- Create a play/pause button with a semi-transparent background (mimicing the portrait frame's background to create an illusion of the 3D model becoming semi-transparent)
    self.frame.portrait.pause = CreateFrame("Button", nil, self.frame.portrait)
    self.frame.portrait.pause:SetFrameLevel(self.frame.portrait.model:GetFrameLevel() + 1)
    self.frame.portrait.pause:SetAllPoints()
    self.frame.portrait.pause.background = self.frame.portrait.pause:CreateTexture(nil, "BACKGROUND")
    self.frame.portrait.pause.background:SetAllPoints()
    self.frame.portrait.pause.background:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameBackground]])
    self.frame.portrait.pause.background:SetAlpha(0.75)
    self.frame.portrait.pause:SetNormalTexture([[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameAtlas]])
    self.frame.portrait.pause:GetNormalTexture():ClearAllPoints()
    self.frame.portrait.pause:GetNormalTexture():SetPoint("CENTER")
    self.frame.portrait.pause:GetNormalTexture():SetSize(32, 32)
    self.frame.portrait.pause:SetPushedTexture([[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameAtlas]])
    self.frame.portrait.pause:GetPushedTexture():ClearAllPoints()
    self.frame.portrait.pause:GetPushedTexture():SetPoint("CENTER")
    self.frame.portrait.pause:GetPushedTexture():SetSize(28, 28)
    function self.frame.portrait.pause:Update()
        if Addon.db.char.IsPaused then
            if soundQueueUI.soundQueue:IsPlaying() then
                self.background:Hide()
            else
                self.background:Show()
            end
            self:GetNormalTexture():SetTexCoord(0 / PORTRAIT_ATLAS_SIZE, 93 / PORTRAIT_ATLAS_SIZE, 419 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE)
            self:GetPushedTexture():SetTexCoord(0 / PORTRAIT_ATLAS_SIZE, 93 / PORTRAIT_ATLAS_SIZE, 419 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE)
            self:GetNormalTexture():SetAlpha(MouseIsOver(self) and 1 or 0.75)
        else
            self.background:Hide()
            self:GetNormalTexture():SetTexCoord(93 / PORTRAIT_ATLAS_SIZE, 186 / PORTRAIT_ATLAS_SIZE, 419 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE)
            self:GetPushedTexture():SetTexCoord(93 / PORTRAIT_ATLAS_SIZE, 186 / PORTRAIT_ATLAS_SIZE, 419 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE)
            self:GetNormalTexture():SetAlpha(MouseIsOver(self) and 1 or 0)
        end
    end
    self.frame.portrait.pause:Update()
    self.frame.portrait.pause:HookScript("OnEnter", function(self)
        self:GetNormalTexture():SetAlpha(1)
    end)
    self.frame.portrait.pause:HookScript("OnLeave", function(self)
        self:GetNormalTexture():SetAlpha(Addon.db.char.IsPaused and 0.75 or 0)
    end)
    self.frame.portrait.pause:HookScript("OnClick", function()
        PlaySound(SOUNDKIT.CHAT_SCROLL_BUTTON)
        self.soundQueue:TogglePauseQueue()
    end)

    -- Create an overlay frame above the 3D model to contain the border and any other button that might be placed on the border (like the mover)
    self.frame.portrait.border = CreateFrame("Frame", nil, self.frame.portrait) -- Separate frame to ensure that all contained frames will be drawn over the model
    self.frame.portrait.border:SetFrameLevel(self.frame.portrait.pause:GetFrameLevel() + 1)
    self.frame.portrait.border:SetAllPoints()
    self.frame.portrait.border.texture = self.frame.portrait.border:CreateTexture(nil, "BORDER")
    self.frame.portrait.border.texture:SetSize(PORTRAIT_BORDER_SIZE, PORTRAIT_BORDER_SIZE)
    self.frame.portrait.border.texture:SetPoint("TOPLEFT", -PORTRAIT_BORDER_OUTSET, PORTRAIT_BORDER_OUTSET)
    self.frame.portrait.border.texture:SetPoint("BOTTOMRIGHT", PORTRAIT_BORDER_OUTSET, -PORTRAIT_BORDER_OUTSET)
    self.frame.portrait.border.texture:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameAtlas]])
    self.frame.portrait.border.texture:SetTexCoord(0, PORTRAIT_ATLAS_BORDER_SIZE / PORTRAIT_ATLAS_SIZE, 0, PORTRAIT_ATLAS_BORDER_SIZE / PORTRAIT_ATLAS_SIZE)
end

function SoundQueueUI:InitMover()
    -- Create a button that lets the player drag the frame around
    self.frame.mover = CreateFrame("Button", nil, self.frame.portrait.border)
    self.frame.mover:SetSize(26, 26)
    self.frame.mover:SetPoint("CENTER", self.frame.portrait.border, "BOTTOMLEFT", 5, 6)
    self.frame.mover:SetNormalTexture([[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameAtlas]])
    self.frame.mover:GetNormalTexture():SetTexCoord(462 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE, 462 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE)
    self.frame.mover:GetNormalTexture():ClearAllPoints()
    self.frame.mover:GetNormalTexture():SetPoint("CENTER")
    self.frame.mover:GetNormalTexture():SetSize(16, 16)
    self.frame.mover:SetPushedTexture([[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameAtlas]])
    self.frame.mover:GetPushedTexture():SetTexCoord(462 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE, 462 / PORTRAIT_ATLAS_SIZE, 512 / PORTRAIT_ATLAS_SIZE)
    self.frame.mover:GetPushedTexture():ClearAllPoints()
    self.frame.mover:GetPushedTexture():SetPoint("CENTER")
    self.frame.mover:GetPushedTexture():SetSize(14, 14)
    self.frame.mover.background = self.frame.mover:CreateTexture(nil, "BACKGROUND")
    self.frame.mover.background:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\SettingsButton]])
    self.frame.mover.background:SetPoint("CENTER")
    self.frame.mover.background:SetSize(32, 32)
    self.frame.mover:HookScript("OnEnter", function(self)
        if Addon.db.profile.SoundQueueUI.LockFrame then return end
        SetCursor([[Interface\Cursor\UI-Cursor-Move]])
        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
        GameTooltip:SetText("Move the Frame", HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
        GameTooltip:AddLine("Frame position can be locked in settings.", NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
        GameTooltip:Show()
    end)
    self.frame.mover:HookScript("OnLeave", function(self)
        SetCursor(nil)
        GameTooltip_Hide(self)
    end)
    self.frame.mover:HookScript("OnMouseDown", function()
        if Addon.db.profile.SoundQueueUI.LockFrame then return end
        self.frame:StartMoving()
    end)
    self.frame.mover:HookScript("OnMouseUp", function()
        if Addon.db.profile.SoundQueueUI.LockFrame then return end
        self.frame:StopMovingOrSizing()
    end)
end

function SoundQueueUI:InitMinimapButton()
    local buttons =
    {
        { "LeftButton", "Left Click" },
        { "MiddleButton", "Middle Click" },
        { "RightButton", "Right Click" },
    }
    local object = LibDataBroker:NewDataObject("VoiceOver", {
        type = "launcher",
        text = "VoiceOver",
        icon = [[Interface\AddOns\AI_VoiceOver\Textures\MinimapButton]],

        OnClick = function(self, button)
            local command = Addon.db.profile.MinimapButton.Commands[button]
            if command and command ~= "" then
                local handler = Options.table.args.SlashCommands.args[command]
                if handler and handler.func then
                    PlaySound(SOUNDKIT.CHAT_SCROLL_BUTTON)
                    handler.func()
                end
            end
        end,

        OnTooltipShow = function(tooltip)
            tooltip:SetText("VoiceOver")
            for _, info in ipairs(buttons) do
                local button, text = unpack(info)
                local command = Addon.db.profile.MinimapButton.Commands[button]
                if command and command ~= "" then
                    local handler = Options.table.args.SlashCommands.args[command]
                    if handler and handler.name then
                        tooltip:AddLine(format("%s%s:|r %s", GRAY_FONT_COLOR_CODE, text, handler.name))
                    end
                end
            end
            tooltip:Show()
        end,
    })
    LibDBIcon:Register("VoiceOver", object, Addon.db.profile.MinimapButton.LibDBIcon)
end

function SoundQueueUI:RefreshConfig()
    if Addon.db.profile.SoundQueueUI.HidePortrait then
        if self.frame.portrait:IsShown() then
            self.frame:SetWidth(self.frame:GetWidth() - PORTRAIT_SIZE)
        end
        self.frame:SetResizeBounds(100, PORTRAIT_SIZE, 10000, PORTRAIT_SIZE)

        self.frame.portraitLine:Show()
        self.frame.portrait:Hide()
        self.frame.miniPause:Show()

        self.frame.container:SetPoint("LEFT", 20, 0)
        self.frame.background:SetPoint("TOPLEFT")
        self.frame.background:SetPoint("BOTTOMLEFT")

        self.frame.mover:SetParent(self.frame)
        self.frame.mover:SetPoint("CENTER", self.frame, "BOTTOMLEFT", 2, 6)
    else
        if not self.frame.portrait:IsShown() then
            self.frame:SetWidth(self.frame:GetWidth() + PORTRAIT_SIZE)
        end
        self.frame:SetResizeBounds(PORTRAIT_SIZE + 100, PORTRAIT_SIZE, 10000, PORTRAIT_SIZE)

        self.frame.portraitLine:Hide()
        self.frame.portrait:Show()
        self.frame.miniPause:Hide()

        self.frame.container:SetPoint("LEFT", self.frame.portrait, "RIGHT", 15, 0)
        self.frame.background:SetPoint("TOPLEFT", self.frame.portrait, "TOPRIGHT")
        self.frame.background:SetPoint("BOTTOMLEFT", self.frame.portrait, "BOTTOMRIGHT")

        self.frame.mover:SetParent(self.frame.portrait.border)
        self.frame.mover:SetPoint("CENTER", self.frame.portrait.border, "BOTTOMLEFT", 5, 6)
    end

    self.frame.mover:SetShown(not Addon.db.profile.SoundQueueUI.LockFrame)
    self.frame.resizer:SetShown(not Addon.db.profile.SoundQueueUI.LockFrame)
    self.frame:SetScale(Addon.db.profile.SoundQueueUI.FrameScale)

    self:UpdateSoundQueueDisplay()
    LibDBIcon:Refresh("VoiceOver", Addon.db.profile.MinimapButton.LibDBIcon)
end

function SoundQueueUI:CreateButton(i)
    local soundQueueUI = self

    local button = CreateFrame("Button", nil, self.frame.container)
    self.frame.container.buttons[i] = button

    button:SetID(i)
    button:SetHeight(20)

    button.textWidget = button:CreateFontString(nil, "OVERLAY", "VoiceOverButtonFont")
    button.textWidget:SetJustifyH("LEFT")
    button.textWidget:SetWordWrap(false)

    button.iconWidget = button:CreateTexture(nil, "ARTWORK")
    button.iconWidget:SetSize(16, 16)
    button.iconWidget:SetPoint("CENTER", button, "LEFT", 16 / 2, 0)

    function button:Configure(soundData)
        self.soundData = soundData
        self:Update()
    end
    function button:Update(pushed, hovered)
        if pushed == nil then pushed = self.pushed else self.pushed = pushed end
        if hovered == nil then hovered = self.hovered else self.hovered = hovered end
        local soundData = self.soundData
        if not soundData then
            self:Hide()
            return
        end
        self:Show()
        local soundDataBeingPlayed = soundQueueUI.soundQueue.sounds[1]
        local isBeingPlayed = soundData == soundDataBeingPlayed
        local buttonIndex = self:GetID()

        if isBeingPlayed then
            self:SetAlpha(1)
            self.textWidget:SetShadowColor(0, 0, 0, 1)
            self:SetPoint("TOPLEFT", soundQueueUI.frame.container.name, "BOTTOMLEFT", 0, -2)
            self:EnableMouse(soundQueueUI.soundQueue:CanBePaused())
        else
            local isCollapsedGossipBeingPlayed = soundDataBeingPlayed and Enums.SoundEvent:IsGossipEvent(soundDataBeingPlayed.event) and (HIDE_GOSSIP_OPTIONS or not soundDataBeingPlayed.title)
            local queuePosition = buttonIndex - (isCollapsedGossipBeingPlayed and 0 or 1)
            local alpha = math.max(0.1, math.min(1, 1 - (queuePosition - 1) / 3))
            self:SetAlpha(alpha)
            self.textWidget:SetShadowColor(0, 0, 0, 0.5 + 0.5 * alpha) -- Technically isn't necessary, but it looks better this way
            self:SetPoint("TOPLEFT", soundQueueUI.frame.container.buttons[buttonIndex - 1] or soundQueueUI.frame.container.name, "BOTTOMLEFT", 0, queuePosition == 1 and -8 or -2)
            self:EnableMouse(true)
        end

        self.textWidget:ClearAllPoints()
        self.textWidget:SetPoint("LEFT", 16 + 5, 0)
        self.textWidget:SetText(soundData.title)
        self:SetWidth(math.min(self:GetParent():GetWidth(), 16 + 5 + self.textWidget:GetWidth()))
        self.textWidget:SetPoint("RIGHT")

        if hovered then
            local r, g, b = 225 / 255, 20 / 255, 8 / 255
            if pushed then
                r, g, b = r * 0.75, g * 0.75, b * 0.75
            end
            self:SetAlpha(1)
            self.textWidget:SetTextColor(r, g, b)
            self.textWidget:SetShadowColor(0, 0, 0, 1)
            self.iconWidget:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\SoundQueueBulletDelete]])
            self.iconWidget:SetSize(14, 14)
        else
            if isBeingPlayed then
                local event = soundData.event
                if Enums.SoundEvent:IsQuestEvent(event) then
                    self.textWidget:SetTextColor(245 / 255, 204 / 255, 24 / 255)
                    if event == Enums.SoundEvent.QuestAccept then
                        self.iconWidget:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\SoundQueueBulletAccept]])
                    elseif event == Enums.SoundEvent.QuestProgress then
                        self.iconWidget:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\SoundQueueBulletProgress]])
                    elseif event == Enums.SoundEvent.QuestComplete then
                        self.iconWidget:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\SoundQueueBulletComplete]])
                    end
                elseif Enums.SoundEvent:IsGossipEvent(event) then
                    self.textWidget:SetTextColor(1, 1, 1)
                    self.iconWidget:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\SoundQueueBulletGossip]])
                end
                self.iconWidget:SetSize(14, 14)
            else
                self.textWidget:SetTextColor(123 / 255, 147 / 255, 167 / 255)
                self.iconWidget:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\SoundQueueBulletQueue]])
                self.iconWidget:SetSize(22, 22)
            end
        end
    end

    button:HookScript("OnClick", function(self) soundQueueUI.soundQueue:RemoveSoundFromQueue(self.soundData) end)
    button:HookScript("OnMouseDown", function(self) self:Update(true) end)
    button:HookScript("OnMouseUp", function(self) self:Update(false) end)
    button:HookScript("OnEnter", function(self) self:Update(nil, true) end)
    button:HookScript("OnLeave", function(self) self:Update(nil, false) end)
    button:Update()

    return button
end

function SoundQueueUI:UpdateSoundQueueDisplay()
    self.frame:SetShown(not Addon.db.profile.SoundQueueUI.HideFrame and #self.soundQueue.sounds > 0)

    self:UpdatePauseDisplay()

    self.frame.portrait:Configure(self.soundQueue.sounds[1])

    self.frame.container:Show()
    self.frame.container:SetHeight(self.frame:GetHeight())
    local gossipCount
    local lastButtonIndex = 0
    local lastContent
    local nameSet
    for i, soundData in ipairs(self.soundQueue.sounds) do
        if not nameSet then
            nameSet = true
            self.frame.container.name:SetText(soundData.name)
            lastContent = self.frame.container.name
        end
        if not Enums.SoundEvent:IsGossipEvent(soundData.event) or (not HIDE_GOSSIP_OPTIONS and soundData.title) then
            if not gossipCount then
                gossipCount = i - 1
            end
            lastButtonIndex = lastButtonIndex + 1
            local button = self.frame.container.buttons[lastButtonIndex] or self:CreateButton(lastButtonIndex)
            button:Configure(soundData)
            lastContent = button
        end
        if lastButtonIndex == 4 then
            break
        end
    end
    for i = lastButtonIndex + 1, #self.frame.container.buttons do
        self.frame.container.buttons[i]:Configure(nil)
    end

    self.frame.container.stopGossip:SetGossipCount(gossipCount or #self.soundQueue.sounds)
    self.frame.container.name:Update()

    -- Align the container vertically to the middle
    if lastContent then
        local contentTop = self.frame.container.name:GetTop()
        local contentBottom = lastContent:GetBottom()
        self.frame.container:SetHeight(contentTop - contentBottom)
    else
        self.frame.container:Hide()
    end
    -- Refresh again after updating layout (same frame in modern WoW, next frame in old WoW) to update name truncation and button hover state depending on the new button positions after realignment
    Addon:ScheduleTimer(function()
        self.frame.container.buttons:Update()
        self.frame.container.name:Update()
    end, 0)
end

function SoundQueueUI:UpdatePauseDisplay()
    self.frame.miniPause:Update()
    self.frame.portrait.pause:Update()
end
