setfenv(1, VoiceOver)
SoundQueueUI = {}

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

-- Listas de modelos especiales (compartidas con AlternativePortrait y RareTruePortrait)
ModelCameraConfig = {
    uniqueModelIDs = {
        878772, 878773, 878774, 878775, 4519034, 576555,
        5548261, 5489804, 5353632, 5767091, 893080, 4220448, 986648, 1721003, 5482015, 968705, 2618947,
        5794846, 4207724, 5517447, 997378, 4217881,
        1890759, 3024833, 4498203, 5764885, 119376, 3058051,
        6047739, 5145420
    },
    fullBodyModelIDs = {
        1717164, 1000764
    },
    specialModelIDs = {
        1066978, 959310, 5680838
    }
}

local PORTRAIT_SIZE = 120
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 = false -- Disable to allow gossip options to show up in the button list, like quests
local WAIT_FOR_ANIMATION_FINISH_BEFORE_IDLE = true
local CAN_MODEL_LOAD_CACHE = true
local GOSSIP_MAX_CHARS = 132

-- Helper function to remove line breaks from text
local function RemoveLineBreaks(text)
    if not text then return "" end
    return text:gsub("\r\n", " "):gsub("\n", " "):gsub("\r", " "):gsub("%s+", " ")
end

-- Helper function to split text into segments at punctuation marks
-- Returns a table of text segments, each up to maxChars, split at punctuation
function SplitTextIntoSegments(text, maxChars)
    if not text then return { "" } end
    text = RemoveLineBreaks(text)
    if #text <= maxChars then return { text } end

    local segments = {}
    local remaining = text
    local punctuation = "[%.,%?!;:]"

    while #remaining > 0 do
        if #remaining <= maxChars then
            table.insert(segments, remaining)
            break
        end

        -- Find the best punctuation mark to cut at, closest to maxChars
        local bestCut = nil

        -- Search for punctuation marks within the limit
        for i = maxChars, 1, -1 do
            local char = remaining:sub(i, i)
            if char:match(punctuation) then
                bestCut = i
                break
            end
        end

        -- If no punctuation found, try to cut at a space
        if not bestCut then
            for i = maxChars, 1, -1 do
                if remaining:sub(i, i) == " " then
                    bestCut = i
                    break
                end
            end
        end

        -- If still no good cut point, just cut at maxChars
        if not bestCut then
            bestCut = maxChars
        end

        table.insert(segments, remaining:sub(1, bestCut))
        remaining = remaining:sub(bestCut + 1)

        -- Trim leading spaces from the next segment
        remaining = remaining:gsub("^%s+", "")
    end

    return segments
end

-- Get the current text segment based on elapsed time and audio duration
-- Time is distributed proportionally based on segment length
function GetCurrentTextSegment(soundData, maxChars)
    if not soundData or not soundData.title then return "" end

    local text = RemoveLineBreaks(soundData.title)
    if #text <= maxChars then return text end

    -- Get or create segments cache
    if not soundData.textSegments then
        soundData.textSegments = SplitTextIntoSegments(text, maxChars)
    end

    local segments = soundData.textSegments
    local numSegments = #segments

    if numSegments <= 1 then return segments[1] or "" end

    -- Calculate total characters across all segments
    local totalChars = 0
    for _, segment in ipairs(segments) do
        totalChars = totalChars + #segment
    end

    -- Calculate which segment to show based on elapsed time
    local duration = soundData.length or 10
    local startTime = soundData.playStartTime or GetTime()
    local elapsed = GetTime() - startTime

    if elapsed < 0 then
        return segments[1] or ""
    end

    -- Find the segment based on proportional time
    local accumulatedTime = 0
    for i, segment in ipairs(segments) do
        -- Time for this segment is proportional to its character count
        local segmentTime = (#segment / totalChars) * duration
        accumulatedTime = accumulatedTime + segmentTime

        if elapsed < accumulatedTime then
            return segment
        end
    end

    -- If we've passed all segments, return the last one
    return segments[numSegments] or ""
end

-- Helper function to get the current atlas texture path
function GetAtlasTexturePath()
    local atlasIndex = Addon.db.profile.SoundQueueUI.PortraitFrameAtlas or 0
    local atlasPath = [[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameAtlas]]
    if atlasIndex > 0 then
        atlasPath = atlasPath .. atlasIndex
    end
    return atlasPath
end

do
    local font = CreateFont("VoiceOverNameFont")
    font:SetFont(GameFontNormal:GetFont(), 16, "")
    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(), 13, "")
    font:SetShadowColor(0, 0, 0)
    font:SetShadowOffset(1, -1)
    font:SetJustifyH("LEFT")
    font:SetJustifyV("MIDDLE")
end

function SoundQueueUI:Initialize()
    self:InitDisplay()
    self:InitPortraitLine()
    self:InitPortrait()
    self:InitMover()
    self:InitMinimapButton()

    self:RefreshConfig()
end

function SoundQueueUI:InitDisplay()
    self.frame = CreateFrame("Frame", "VoiceOverFrame", UIParent, "BackdropTemplate")
    function self.frame:Reset()
        self:SetWidth(PORTRAIT_SIZE + FRAME_WIDTH_WITHOUT_PORTRAIT)
        self:SetHeight(PORTRAIT_SIZE)
        self:ClearAllPoints()
        self:SetPoint("BOTTOM", 0, 200)
    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)

    -- OnUpdate para actualizar el texto de gossip sincronizado con el audio
    self.frame.textUpdateTimer = 0
    self.frame:SetScript("OnUpdate", function(f, elapsed)
        f.textUpdateTimer = f.textUpdateTimer + elapsed
        if f.textUpdateTimer >= 0.1 then -- Actualizar cada 0.1 segundos
            f.textUpdateTimer = 0
            local soundData = SoundQueue:GetCurrentSound()
            if soundData and Enums.SoundEvent:IsGossipEvent(soundData.event) and soundData.title and #soundData.title > GOSSIP_MAX_CHARS then
                -- Solo actualizar si hay múltiples segmentos
                SoundQueueUI.frame.container.buttons:Update()
            end
        end
    end)

    -- 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 = -6
    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()
        local containerWidth = self:GetParent():GetWidth()
        self:SetWidth(0)
        self:SetText(self:GetText())
        self:SetWidth(math.min(
            containerWidth - (SoundQueueUI.frame.container.stopGossip:IsShown() and 32 + SKIP_GOSSIP_BUTTON_OFFSET or 0),
            self:GetStringWidth() + 1))
    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(SoundQueue:CanBePaused() or gossipCount > 1)
        self:SetHighlightTexture(texture, "ADD")
        self:SetNormalTexture(texture)
        self:SetPushedTexture(texture)

        self.tooltip = 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.U_CHAT_SCROLL_BUTTON)
        if SoundQueue:CanBePaused() then
            -- Skip current
            local soundData = SoundQueue:GetCurrentSound()
            if soundData and Enums.SoundEvent:IsGossipEvent(soundData.event) then
                SoundQueue:RemoveSoundFromQueue(soundData)
            end
        else
            -- Skip all remaining
            local soundData = SoundQueue:GetNextSound()
            while soundData and Enums.SoundEvent:IsGossipEvent(soundData.event) do
                SoundQueue:RemoveSoundFromQueue(soundData)
                soundData = SoundQueue:GetNextSound()
            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(GetAtlasTexturePath())
    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(GetAtlasTexturePath())
    self.frame.miniPause:GetNormalTexture():ClearAllPoints()
    self.frame.miniPause:GetNormalTexture():SetPoint("CENTER")
    self.frame.miniPause:GetNormalTexture():SetSize(14, 14)
    self.frame.miniPause:SetPushedTexture(GetAtlasTexturePath())
    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.U_CHAT_SCROLL_BUTTON)
        SoundQueue:TogglePauseQueue()
    end)
end

local function ShouldShowBookFor(soundData)
    if soundData.modelFrame then
        if type(soundData.modelFrame:GetModel()) ~= "string" then
            return true -- Show book for missing and unloaded models
        end
        return false
    end

    -- Si tiene gameObjectID, mostrar el libro (GameObjects no tienen modelo 3D accesible)
    if soundData.gameObjectID then
        return true -- Show book for GameObjects
    end

    local guid = soundData.unitGUID
    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 -- Show book for GameObjects
    end
    return false
end

function SoundQueueUI:InitPortrait()
    -- 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.bookModel:Hide()
            if self.iconTexture then
                self.iconTexture:Hide()
            end
        elseif soundData.iconID then
            -- Mostrar icono de item
            self.model:Hide()
            self.bookModel:Hide()
            if self.iconTexture then
                self.iconTexture:SetTexture(soundData.iconID)
                self.iconTexture:Show()
            end
        elseif ShouldShowBookFor(soundData) then
            self.model:Hide()
            self.bookModel:Show()

            -- Para GameObjects, siempre mostrar el libro genérico (15608)
            if soundData.gameObjectID or soundData.isGameObject then
                self.bookModel:SetCreature(15608) -- Libro genérico
            elseif soundData.displayID then
                self.bookModel:SetDisplayInfo(soundData.displayID)
            else
                local creatureID = soundData.creatureID or 15608
                self.bookModel:SetCreature(creatureID)
            end
            self.bookModel:SetCustomCamera(0) -- Agregar la cámara 0 para el libro
            self.bookModel:SetAnimation(60)
            self.bookModel:SetScript("OnUpdate", function(self, elapsed)
                if not self.animDuration then
                    self.animDuration = Utils:GetModelAnimationDuration(self:GetModelFileID(), 60)
                end
                if not self.animSequence then
                    self.animSequence = { 60, 65 }
                    self.animIndex = 1
                end
                if GetTime() - (self.animtimer or 0) >= (self.animDuration or 2) + 0.2 then
                    self.animtimer = GetTime()
                    self:SetAnimation(self.animSequence[self.animIndex])
                    self.animIndex = self.animIndex % #self.animSequence + 1
                    self.animDuration = Utils:GetModelAnimationDuration(self:GetModelFileID(),
                        self.animSequence[self.animIndex])
                end
            end)
        else
            -- Mostrar modelo 3D normal
            if self.iconTexture then
                self.iconTexture:Hide()
            end

            self.model = soundData.modelFrame or self.model

            if not self.model._initialized then
                self.model._initialized = true
                self.model:HookScript("OnHide", function(self)
                    self:ClearModel()
                    self.oldCreatureID = nil
                    self.oldDisplayID = nil
                    self.animation = nil
                    self.animDuration = nil
                    self.animDelay = nil
                    self.animtimer = nil
                    self.modelIDPrinted = nil
                end)
                self.model:HookScript("OnUpdate", function(self, elapsed)
                    if CAN_MODEL_LOAD_CACHE and self.oldCreatureID and not self:GetModelFileID() then
                        self:SetCreature(self.oldCreatureID)
                        if not self:GetModelFileID() then
                            return
                        end
                    end
                    local modelID = self:GetModelFileID()
                    if tContains(ModelCameraConfig.uniqueModelIDs, modelID) then
                        self:SetCustomCamera(1)

                        self:SetRotation(0.5)        -- Rotación en radianes (0-6.28)
                        self:SetCameraDistance(1.10) -- Distancia de la cámara al modelo
                        self:SetCameraFacing(0)      -- Ángulo de la cámara en radianes
                    elseif tContains(ModelCameraConfig.specialModelIDs, modelID) then
                        self:SetCustomCamera(1)
                        --self:ClearModel()
                        --self:SetCreature(1066978)
                        --self:SetCameraPosition(0, 2, 1)  -- Posición de la cámara
                        --self:SetCameraTarget(0, 0, 0)    -- Punto de enfoque
                        --self:SetCameraDistance(2.5)      -- Distancia
                        --self:SetCameraFacing(0)          -- Ángulo
                        self:SetPosition(0, -0.2, -0.1)
                        self:SetScale(0.85)
                        self:SetRotation(0.5)               -- Escala del modelo
                        --self:SetAnimation(60)            -- Animación
                        -- Posición del modelo
                        --self:SetFacing(0)   
                        -- Rotación del modelo
                    elseif tContains(ModelCameraConfig.fullBodyModelIDs, modelID) then
                        -- Modelos que muestran cuerpo completo - zoom al busto/rostro
                        self:SetCustomCamera(1)
                        self:SetPosition(0, 0, -0.6)        -- Subir la cámara (Z negativo = sube el modelo)
                        self:SetCameraDistance(0.6)         -- Acercar mucho la cámara
                        self:SetRotation(0.5)               -- Rotación ligera
                    else
                        self:SetCustomCamera(0)
                    end
                    if UnitName("player") == "Arckanys" or UnitName("player") == "Disparos" or UnitName("player") == "Maguno" then
                        if not self.modelIDPrinted then
                            DEFAULT_CHAT_FRAME:AddMessage("Interacted ModelID: " .. modelID, 0, 1, 0)
                            self.modelIDPrinted = true
                        end
                    end
                    if self.animation and not self.animDuration then
                        self.animDuration = Utils:GetModelAnimationDuration(self:GetModelFileID(), self.animation)
                        if self.animDuration and self.animDuration == 0 then
                            self.animation = 0
                            self:SetAnimation(self.animation)
                        end
                    end
                    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 SoundQueue:IsPlaying()
                    if not WAIT_FOR_ANIMATION_FINISH_BEFORE_IDLE and self.animation and self.animation ~= 0 and not self.animDelay and isPaused then
                        self.animation = 0
                        self.animtimer = nil
                        self:SetAnimation(self.animation)
                    end
                    if not self.animSequence then
                        self.animSequence = { 60, 65 }
                        self.animIndex = 1
                    end
                    if self.animation and GetTime() - (self.animtimer or 0) >= (self.animDuration or 2) + 0.2 then
                        if isPaused or self.animation == 0 then
                            if WAIT_FOR_ANIMATION_FINISH_BEFORE_IDLE and self.animation ~= 0 and not self.animDelay then
                                self.animation = 0
                                self.animtimer = nil
                                self:SetAnimation(self.animation)
                            end
                        else
                            self.animtimer = GetTime()
                            self.animation = self.animSequence[self.animIndex]
                            self:SetAnimation(self.animation)
                            self.animIndex = self.animIndex % #self.animSequence + 1
                            self.animDuration = Utils:GetModelAnimationDuration(self:GetModelFileID(), self.animation)
                        end
                    end
                    self.wasPaused = isPaused
                end)
            end
            self.model:Show()
            self.bookModel:Hide()
            self.model:SetAllPoints()

            -- DisplayID por nombre de NPC — misma logica que AlternativePortrait.
            local displayIDByName = {
                ["Arator"] = 124777,
            }
            if not soundData.displayID and soundData.name and displayIDByName[soundData.name] then
                soundData.displayID = displayIDByName[soundData.name]
            end

            -- Si hay displayID, usar SetDisplayInfo; sino usar SetCreature
                if soundData.displayID then
                local displayID = soundData.displayID
                if displayID ~= self.model.oldDisplayID then
                    if CAN_MODEL_LOAD_CACHE then
                        self.model:ClearModel()
                    end
                    self.model:SetDisplayInfo(displayID)
                    self.model:SetCustomCamera(0)
                    -- Usar animación 0 (idle) si useIdleAnimation es true, sino 60 (hablar)
                    self.model.animation = soundData.useIdleAnimation and 0 or 60
                    self.model.animDuration = nil
                    self.model.animDelay = soundData.delay
                    self.model.animtimer = nil
                    self.model.oldDisplayID = displayID
                    self.model.oldCreatureID = nil -- Limpiar creatureID
                elseif self.model.wasPaused and SoundQueue:IsPlaying() then
                    self.model.animation = soundData.useIdleAnimation and 0 or 60
                    self.model.animDuration = nil
                    self.model.animDelay = soundData.delay
                end
            else
                -- Si no hay displayID, usar creatureID como antes
                local creatureID
                if soundData.unitGUID then
                    creatureID = Utils:GetIDFromGUID(soundData.unitGUID)
                elseif soundData.creatureID then
                    creatureID = soundData.creatureID
                else
                    creatureID = soundData.id
                end
                if creatureID ~= self.model.oldCreatureID then
                    if CAN_MODEL_LOAD_CACHE then
                        self.model:ClearModel()
                    end
                    self.model:SetCreature(creatureID)
                    self.model:SetCustomCamera(0)
                    -- Usar animación 0 (idle) si useIdleAnimation es true, sino 60 (hablar)
                    self.model.animation = soundData.useIdleAnimation and 0 or 60
                    self.model.animDuration = nil
                    self.model.animDelay = soundData.delay
                    self.model.animtimer = nil
                    self.model.oldCreatureID = creatureID
                    self.model.oldDisplayID = nil -- Limpiar displayID
                elseif self.model.wasPaused and SoundQueue:IsPlaying() then
                    self.model.animation = soundData.useIdleAnimation and 0 or 60
                    self.model.animDuration = nil
                    self.model.animDelay = soundData.delay
                end
            end
        end
    end

    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]] .. (Addon.db.profile.SoundQueueUI.PortraitBackgroundTexture or 0))
    self.frame.portrait.model = CreateFrame("DressUpModel", nil, self.frame.portrait)
    self.frame.portrait.defaultModel = self.frame.portrait.model
    self.frame.portrait.bookModel = CreateFrame("DressUpModel", nil, self.frame.portrait)
    self.frame.portrait.bookModel:SetAllPoints()
    self.frame.portrait.bookModel:SetCreature(15608)
    self.frame.portrait.bookModel:HookScript("OnShow", function(self)
        self:SetCustomCamera(0)
    end)
    self.frame.portrait.bookModel:SetAnimation(60)
    self.frame.portrait.bookModel:HookScript("OnUpdate", function(self, elapsed)
        if not self.animDuration then
            self.animDuration = Utils:GetModelAnimationDuration(self:GetModelFileID(), 60)
        end
        if not self.animSequence then
            self.animSequence = { 60, 60, 65, 60, 60 }
            self.animIndex = 1
        end
        if GetTime() - (self.animtimer or 0) >= (self.animDuration or 2) + 0.2 then
            self.animtimer = GetTime()
            self:SetAnimation(self.animSequence[self.animIndex])
            self.animIndex = self.animIndex % #self.animSequence + 1
            self.animDuration = Utils:GetModelAnimationDuration(self:GetModelFileID(), self.animSequence[self.animIndex])
        end
    end)
    self.frame.portrait.bookModel:Hide()

    -- Crear texture para iconos de items
    self.frame.portrait.iconTexture = self.frame.portrait:CreateTexture(nil, "ARTWORK")
    self.frame.portrait.iconTexture:SetAllPoints()
    self.frame.portrait.iconTexture:Hide()

    -- Create PlayerModel for visual effect animation overlay
    self.frame.portrait.effectModel = CreateFrame("PlayerModel", nil, UIParent)
    self.frame.portrait.effectModel:SetKeepModelOnHide(true)
    self.frame.portrait.effectModel:SetFrameLevel(self.frame.portrait:GetFrameLevel() + 5) -- Above everything

    -- Apply detached/attached state
    local isDetached = Addon.db.profile.SoundQueueUI.PortraitEffectDetached
    if isDetached then
        -- Detached mode: movable and independent
        self.frame.portrait.effectModel:SetParent(UIParent)
        self.frame.portrait.effectModel:SetMovable(true)
        self.frame.portrait.effectModel:EnableMouse(true)
        self.frame.portrait.effectModel:RegisterForDrag("LeftButton")
        self.frame.portrait.effectModel:SetScript("OnDragStart", function(self) self:StartMoving() end)
        self.frame.portrait.effectModel:SetScript("OnDragStop", function(self)
            self:StopMovingOrSizing()
            -- Save position
            local point, _, relativePoint, x, y = self:GetPoint()
            Addon.db.profile.SoundQueueUI.PortraitEffectPosition = {
                point = point,
                relativePoint = relativePoint,
                x = x,
                y = y
            }
        end)
        -- Restore saved position or default
        local pos = Addon.db.profile.SoundQueueUI.PortraitEffectPosition
        if pos then
            self.frame.portrait.effectModel:SetPoint(pos.point, UIParent, pos.relativePoint, pos.x, pos.y)
        else
            self.frame.portrait.effectModel:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
        end
        local frameSize = Addon.db.profile.SoundQueueUI.PortraitEffectFrameSize or 120
        self.frame.portrait.effectModel:SetSize(frameSize, frameSize)
    else
        -- Attached mode: follows portrait
        self.frame.portrait.effectModel:ClearAllPoints()
        self.frame.portrait.effectModel:SetAllPoints(self.frame.portrait)
        self.frame.portrait.effectModel:SetParent(self.frame.portrait)
    end

    -- Show/hide effect based on enabled setting
    local effectEnabled = Addon.db.profile.SoundQueueUI.PortraitEffectEnabled
    if effectEnabled == nil then effectEnabled = false end -- Default to disabled
    if effectEnabled then
        self.frame.portrait.effectModel:Show()
    else
        self.frame.portrait.effectModel:Hide()
    end

    -- Configure the visual effect model using saved FileDataID
    local effectFileID = Addon.db.profile.SoundQueueUI.PortraitEffectFileID or 6217457
    local success = pcall(self.frame.portrait.effectModel.SetModel, self.frame.portrait.effectModel, effectFileID)
    if success then
        self.frame.portrait.effectModel:SetPortraitZoom(0)
        self.frame.portrait.effectModel:ClearTransform()

        -- Apply saved position (X, Y, Z)
        local x = Addon.db.profile.SoundQueueUI.PortraitEffectX or 0
        local y = Addon.db.profile.SoundQueueUI.PortraitEffectY or 0
        local z = Addon.db.profile.SoundQueueUI.PortraitEffectZ or 0
        self.frame.portrait.effectModel:SetPosition(z, x, y) -- Note: SetPosition takes (z, x, y)

        -- Apply saved rotation
        local rotation = Addon.db.profile.SoundQueueUI.PortraitEffectRotation or 0
        local radians = math.rad(rotation)
        self.frame.portrait.effectModel:SetFacing(radians)

        self.frame.portrait.effectModel:SetAnimation(0)
    end

    -- Hook portrait visibility to control effectModel when detached
    if Addon.db.profile.SoundQueueUI.PortraitEffectDetached then
        -- When detached, effectModel should follow portrait visibility
        self.frame.portrait:HookScript("OnShow", function()
            if self.frame.portrait.effectModel then
                self.frame.portrait.effectModel:Show()
            end
        end)
        self.frame.portrait:HookScript("OnHide", function()
            if self.frame.portrait.effectModel then
                self.frame.portrait.effectModel:Hide()
            end
        end)

        -- Set initial visibility based on portrait state
        if self.frame.portrait:IsShown() then
            self.frame.portrait.effectModel:Show()
        else
            self.frame.portrait.effectModel:Hide()
        end
    end

    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]] .. (Addon.db.profile.SoundQueueUI.PortraitBackgroundTexture or 0))
    self.frame.portrait.pause.background:SetAlpha(0.75)
    self.frame.portrait.pause:SetNormalTexture(GetAtlasTexturePath())
    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(GetAtlasTexturePath())
    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 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.U_CHAT_SCROLL_BUTTON)
        SoundQueue:TogglePauseQueue()
    end)
    self.frame.portrait.border = CreateFrame("Frame", nil, self.frame.portrait)
    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(GetAtlasTexturePath())
    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(GetAtlasTexturePath())
    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(GetAtlasTexturePath())
    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()
    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.U_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()
    -- Check portrait style - if Midnight, hide classic and show alternative
    local useMidnightStyle = Addon.db.profile.SoundQueueUI.PortraitStyle == 1
    
    if useMidnightStyle then
        -- Ocultar el frame clásico
        self.frame:Hide()
        -- Inicializar AlternativePortrait si aún no existe
        if AlternativePortrait and not AlternativePortrait.frame then
            AlternativePortrait:Initialize()
        end
        -- Actualizar AlternativePortrait
        if AlternativePortrait then
            AlternativePortrait:RefreshConfig()
        end
        return -- No procesar más configuración del estilo clásico
    end
    
    -- Si no es Midnight, ocultar AlternativePortrait si existe
    if AlternativePortrait and AlternativePortrait.frame then
        AlternativePortrait:Hide()
    end

    -- Update background textures
    local textureIndex = Addon.db.profile.SoundQueueUI.PortraitBackgroundTexture or 0
    local texturePath = [[Interface\AddOns\AI_VoiceOver\Textures\PortraitFrameBackground]] .. textureIndex
    if self.frame.portrait.background then
        self.frame.portrait.background:SetTexture(texturePath)
    end
    if self.frame.portrait.pause.background then
        self.frame.portrait.pause.background:SetTexture(texturePath)
    end

    -- Update atlas textures
    local atlasPath = GetAtlasTexturePath()
    if self.frame.portraitLine then
        self.frame.portraitLine:SetTexture(atlasPath)
    end
    if self.frame.miniPause then
        self.frame.miniPause:SetNormalTexture(atlasPath)
        self.frame.miniPause:SetPushedTexture(atlasPath)
    end
    if self.frame.portrait.pause then
        self.frame.portrait.pause:SetNormalTexture(atlasPath)
        self.frame.portrait.pause:SetPushedTexture(atlasPath)
    end
    if self.frame.portrait.border and self.frame.portrait.border.texture then
        self.frame.portrait.border.texture:SetTexture(atlasPath)
    end
    if self.frame.mover then
        self.frame.mover:SetNormalTexture(atlasPath)
        self.frame.mover:SetPushedTexture(atlasPath)
    end

    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 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:SetWordWrap(true)

    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 = SoundQueue:GetCurrentSound()
        local isBeingPlayed = soundData == soundDataBeingPlayed
        local buttonIndex = self:GetID()

        if isBeingPlayed then
            self:SetAlpha(1)
            self.textWidget:SetShadowColor(0, 0, 0, 1)
            -- Mayor espaciado para diálogos (añade un enter/espacio)
            local verticalOffset = (soundData.event == Enums.SoundEvent.Dialogue) and -15 or -2
            self:SetPoint("TOPLEFT", SoundQueueUI.frame.container.name, "BOTTOMLEFT", 0, verticalOffset)
            self:EnableMouse(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)

        -- Truncar texto según el tipo de evento
        local displayText = soundData.title
        if (soundData.event == Enums.SoundEvent.Dialogue or soundData.event == Enums.SoundEvent.ItemReceived) and not isBeingPlayed then
            -- Para diálogos e items en cola, truncar a 20 caracteres
            if soundData.title and #soundData.title > 20 then
                displayText = soundData.title:sub(1, 20) .. "..."
            end
        elseif Enums.SoundEvent:IsGossipEvent(soundData.event) and soundData.title then
            -- Para gossips, mostrar el segmento actual sincronizado con el audio
            if isBeingPlayed then
                displayText = GetCurrentTextSegment(soundData, GOSSIP_MAX_CHARS)
            else
                -- En cola, solo mostrar inicio truncado
                local segments = SplitTextIntoSegments(soundData.title, GOSSIP_MAX_CHARS)
                displayText = segments[1] or ""
                if #segments > 1 then
                    displayText = displayText:sub(1, 20) .. "..."
                end
            end
        end

        self.textWidget:SetText(displayText)
        local containerWidth = self:GetParent():GetWidth()
        self:SetWidth(math.min(containerWidth, 16 + 5 + self.textWidget:GetWidth() + 1))
        self.textWidget:SetPoint("RIGHT")

        -- IMPORTANTE: Ajuste automático de altura para eventos con texto largo (word wrap)
        -- Para nuevos eventos que usen 'title' con texto largo (como Items, Dialogues),
        -- el botón ajustará su altura automáticamente basándose en cuántas líneas ocupa el texto.
        -- Esto evita que el texto se sobrepongan al 'name' que se muestra arriba.
        local textHeight = self.textWidget:GetStringHeight()
        if textHeight > 0 then
            self:SetHeight(math.max(20, textHeight + 4)) -- Mínimo 20, +4 para padding
        end

        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 event == Enums.SoundEvent.Dialogue then
                    -- Diálogos usan el mismo color amarillo que los quests
                    self.textWidget:SetTextColor(245 / 255, 204 / 255, 24 / 255)
                    self.iconWidget:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\SoundQueueBulletGossip]])
                elseif event == Enums.SoundEvent.ItemReceived then
                    -- Items usan color blanco para el texto del botón
                    self.textWidget:SetTextColor(1, 1, 1)
                    self.iconWidget:SetTexture([[Interface\AddOns\AI_VoiceOver\Textures\SoundQueueBulletGossip]])
                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) 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()
    -- Check if using Midnight style
    local useMidnightStyle = Addon.db.profile.SoundQueueUI.PortraitStyle == 1
    
    if useMidnightStyle then
        -- Para el estilo Midnight, usar AlternativePortrait
        if AlternativePortrait and AlternativePortrait.frame then
            local isEmpty = SoundQueue:IsEmpty()
            local hideFrame = Addon.db.profile.SoundQueueUI.HideFrame
            
            if not hideFrame and not isEmpty then
                local soundData = SoundQueue:GetCurrentSound()
                AlternativePortrait:Update(soundData)
                AlternativePortrait:Show()
            else
                AlternativePortrait:Hide()
            end
        end
        -- Ocultar el frame clásico
        self.frame:Hide()
        return
    end
    
    self.frame:SetShown(not Addon.db.profile.SoundQueueUI.HideFrame and not SoundQueue:IsEmpty())

    self:UpdatePauseDisplay()

    self.frame.portrait:Configure(SoundQueue:GetCurrentSound())

    self.frame.container:SetHeight(self.frame:GetHeight())
    self.frame.container:Hide()
    self.frame.container:Show()
    local gossipCount
    local lastButtonIndex = 0
    local lastContent
    local nameSet

    for i, soundData in ipairs(SoundQueue.sounds) do
        if not nameSet then
            nameSet = true
            self.frame.container.name:SetText(soundData.name)

            -- Color dorado para items, gris para otros
            if soundData.event == Enums.SoundEvent.ItemReceived then
                self.frame.container.name:SetTextColor(245 / 255, 204 / 255, 24 / 255) -- Dorado
            else
                self.frame.container.name:SetTextColor(214 / 255, 214 / 255, 214 / 255) -- Gris
            end

            lastContent = self.frame.container.name
        end
        
        -- Si hideFromQueue es true y no es el primer item, no mostrar en la lista
        local showInQueue = not soundData.hideFromQueue or i == 1
        
        if showInQueue and (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, getn(self.frame.container.buttons) do
        self.frame.container.buttons[i]:Configure(nil)
    end

    self.frame.container.stopGossip:SetGossipCount(gossipCount or SoundQueue:GetQueueSize())
    self.frame.container.name:Update()

    -- Align the container vertically to the middle
    if lastContent then
        local contentTop = self.frame.container.name:GetTop() or 0
        local contentBottom = lastContent:GetBottom() or 0
        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
