-- AlternativePortrait.lua - Estilo alternativo de retrato "Midnight"
setfenv(1, VoiceOver)
AlternativePortrait = {}

local FRAME_WIDTH = 740
local FRAME_HEIGHT = 370
local PORTRAIT_ATLAS_SIZE = 512

-- Función auxiliar para subcadena UTF-8 (para el efecto de máquina de escribir)
local function utf8sub(str, start, numChars)
    local startIndex = 1
    while start > 1 do
        local byte = string.byte(str, startIndex)
        if not byte then break end
        if byte < 128 then startIndex = startIndex + 1
        elseif byte < 224 then startIndex = startIndex + 2
        elseif byte < 240 then startIndex = startIndex + 3
        else startIndex = startIndex + 4 end
        start = start - 1
    end
    local currentIndex = startIndex
    while numChars > 0 do
        local byte = string.byte(str, currentIndex)
        if not byte then break end
        if byte < 128 then currentIndex = currentIndex + 1
        elseif byte < 224 then currentIndex = currentIndex + 2
        elseif byte < 240 then currentIndex = currentIndex + 3
        else currentIndex = currentIndex + 4 end
        numChars = numChars - 1
    end
    return string.sub(str, startIndex, currentIndex - 1)
end

function AlternativePortrait:Initialize()
    -- Crear el frame principal
    self.frame = CreateFrame("Frame", "VoiceOverAlternativePortrait", UIParent)
    self.frame:SetSize(FRAME_WIDTH, FRAME_HEIGHT)
    self.frame:SetPoint("CENTER", UIParent, "CENTER", 0, -150)
    self.frame:SetFrameStrata("HIGH")
    self.frame:SetFrameLevel(50)
    
    -- Imagen de fondo
    -- (Eliminado el fondo directo del frame principal para usar un overlay más adelante)
    
    -- Hacer arrastrable
    self.frame:EnableMouse(true)
    self.frame:SetMovable(true)
    self.frame:RegisterForDrag("LeftButton")
    self.frame:SetScript("OnDragStart", function(f) 
        if not Addon.db.profile.SoundQueueUI.LockFrame then
            f:StartMoving() 
        end
    end)
    self.frame:SetScript("OnDragStop", function(f) 
        f:StopMovingOrSizing()
        -- Guardar posición
        local point, _, relPoint, x, y = f:GetPoint()
        Addon.db.profile.AlternativePortrait = Addon.db.profile.AlternativePortrait or {}
        Addon.db.profile.AlternativePortrait.position = {
            point = point,
            relPoint = relPoint,
            x = x,
            y = y
        }
    end)
    
    -- Clic derecho para cerrar
    self.frame:SetScript("OnMouseDown", function(f, button)
        if button == "RightButton" then
            f:Hide()
        end
    end)
    
    local defaults = AlternativePortraitDefaults.normal
    
    -- TEXTO 1: Nombre del NPC (Fijo)
    self.frame.titleFrame = CreateFrame("Frame", nil, self.frame)
    self.frame.titleFrame:SetSize(330, 60)
    self.frame.titleFrame:SetPoint("LEFT", self.frame, "CENTER", defaults.title.x, defaults.title.y)
    self.frame.titleFrame:SetFrameLevel(self.frame:GetFrameLevel() + 20)
    
    self.frame.title = self.frame.titleFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    self.frame.title:SetPoint("TOPLEFT", self.frame.titleFrame, "TOPLEFT", 0, 0)
    self.frame.title:SetWidth(450)
    self.frame.title:SetWordWrap(false)
    self.frame.title:SetJustifyH("LEFT")
    self.frame.title:SetJustifyV("TOP")
    self.frame.title:SetFont("Fonts\\FRIZQT__.TTF", 24, "OUTLINE")
    self.frame.title:SetTextColor(1, 1, 1) -- Blanco
    self.frame.title:SetText("Nombre del NPC")
    
    -- TEXTO 2: Título de la misión (Fijo)
    self.frame.descFrame = CreateFrame("Frame", nil, self.frame)
    self.frame.descFrame:SetSize(280, 120)
    self.frame.descFrame:SetPoint("LEFT", self.frame, "CENTER", defaults.desc.x, defaults.desc.y)
    self.frame.descFrame:SetFrameLevel(self.frame:GetFrameLevel() + 20)
    
    self.frame.description = self.frame.descFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    self.frame.description:SetPoint("TOPLEFT", self.frame.descFrame, "TOPLEFT", 0, 0)
    self.frame.description:SetWidth(280)
    self.frame.description:SetWordWrap(true)
    self.frame.description:SetJustifyH("LEFT")
    self.frame.description:SetJustifyV("TOP")
    self.frame.description:SetFont("Fonts\\FRIZQT__.TTF", 16, "OUTLINE")
    self.frame.description:SetTextColor(1, 0.82, 0) -- Dorado
    self.frame.description:SetText("Título de la Misión")
    
    -- CONTENEDOR DE ICONOS DE LOOT (Para rares)
    self.frame.lootFrame = CreateFrame("Frame", nil, self.frame)
    self.frame.lootFrame:SetSize(280, 50)
    self.frame.lootFrame:SetPoint("TOPLEFT", self.frame.description, "BOTTOMLEFT", 0, -8)
    self.frame.lootFrame:SetFrameLevel(self.frame:GetFrameLevel() + 21)
    self.frame.lootFrame:Hide()
    self.frame.lootIcons = {}

    -- ICONO DE MISION (Fijo)
    self.frame.iconFrame = CreateFrame("Frame", nil, self.frame)
    self.frame.iconFrame:SetSize(26, 26)
    self.frame.iconFrame:SetPoint("CENTER", self.frame, "CENTER", defaults.icon.x, defaults.icon.y)
    self.frame.iconFrame:SetFrameLevel(self.frame:GetFrameLevel() + 25)

    self.frame.questIcon = self.frame.iconFrame:CreateTexture(nil, "OVERLAY")
    self.frame.questIcon:SetTexture("Interface\\AddOns\\AI_VoiceOver\\Textures\\quest-campaign-available.blp")
    self.frame.questIcon:SetAllPoints(self.frame.iconFrame)
    
    -- LOGO (Fijo)
    self.frame.logoFrame = CreateFrame("Frame", nil, self.frame)
    self.frame.logoFrame:SetSize(126, 126)
    self.frame.logoFrame:SetPoint("CENTER", self.frame, "CENTER", -234, -71)
    self.frame.logoFrame:SetFrameLevel(self.frame:GetFrameLevel() + 20)
    
    self.frame.logo = self.frame.logoFrame:CreateTexture(nil, "OVERLAY")
    self.frame.logo:SetTexture("Interface\\AddOns\\AI_VoiceOver\\Textures\\LOGO.blp")
    self.frame.logo:SetAllPoints(self.frame.logoFrame)
    
    -- FONDO FIJO (CAPA MÁS BAJA)
    self.frame.bgFrame = CreateFrame("Frame", nil, self.frame)
    self.frame.bgFrame:SetSize(150, 150)
    self.frame.bgFrame:SetPoint("CENTER", self.frame, "CENTER", -229, 0)
    self.frame.bgFrame:SetFrameLevel(self.frame:GetFrameLevel() + 1)
    
    self.frame.modelBackground = self.frame.bgFrame:CreateTexture(nil, "BACKGROUND")
    self.frame.modelBackground:SetTexture("Interface\\AddOns\\AI_VoiceOver\\Textures\\PortraitFrameBackground1")
    self.frame.modelBackground:SetAllPoints(self.frame.bgFrame)
    
    -- MODELO 3D (Arrastrable)
    self.frame.modelFrame = CreateFrame("Frame", nil, self.frame)
    self.frame.modelFrame:SetSize(defaults.model.size, defaults.model.size)
    self.frame.modelFrame:SetPoint("CENTER", self.frame, "CENTER", defaults.model.x, defaults.model.y)
    self.frame.modelFrame:SetFrameLevel(self.frame:GetFrameLevel() + 5)
    self.frame.modelFrame:EnableMouse(true)
    self.frame.modelFrame:SetMovable(true)
    self.frame.modelFrame:RegisterForDrag("LeftButton")
    
    -- Crear el modelo 3D
    self.frame.model = CreateFrame("DressUpModel", nil, self.frame.modelFrame)
    self.frame.model:SetAllPoints(self.frame.modelFrame)
    self.frame.model:SetCreature(15608)
    self.frame.model:SetCustomCamera(0)

    self.frame.model:HookScript("OnHide", function(model)
        model:ClearModel()
        model.oldCreatureID = nil
        model.oldDisplayID = nil
    end)

    -- OnUpdate para cámaras Y animaciones (usa ModelCameraConfig de SoundQueueUI)
    self.frame.model:HookScript("OnUpdate", function(model, elapsed)
        local modelID = model:GetModelFileID()
        if not modelID then return end

        -- Aplicar cámaras usando la configuración global
        if tContains(ModelCameraConfig.uniqueModelIDs, modelID) then
            model:SetCustomCamera(1)
            model:SetRotation(0.5)
            model:SetCameraDistance(1.10)
            model:SetCameraFacing(0)
        elseif tContains(ModelCameraConfig.specialModelIDs, modelID) then
            model:SetCustomCamera(1)
            model:SetPosition(0, -0.2, -0.1)
            model:SetScale(0.85)
            model:SetRotation(0.5)
        elseif tContains(ModelCameraConfig.fullBodyModelIDs, modelID) then
            model:SetCustomCamera(1)
            model:SetPosition(0, 0, -0.6)
            model:SetCameraDistance(0.6)
            model:SetRotation(0.5)
        else
            model:SetCustomCamera(0)
        end
        
        -- Lógica de animación exacta
        if not model.animSequence then
            model.animSequence = { 60, 65 }
            model.animIndex = 1
        end
        
        if not model.animDuration then
            model.animDuration = Utils:GetModelAnimationDuration(model:GetModelFileID(), 60) or 2
        end

        local isPaused = not SoundQueue:IsPlaying()
        if isPaused then
            if model.animation ~= 0 then
                model.animation = 0
                model:SetAnimation(0)
            end
            return
        end

        if GetTime() - (model.animtimer or 0) >= (model.animDuration or 2) + 0.2 then
            model.animtimer = GetTime()
            model.animation = model.animSequence[model.animIndex]
            model:SetAnimation(model.animation)
            model.animIndex = model.animIndex % #model.animSequence + 1
            model.animDuration = Utils:GetModelAnimationDuration(model:GetModelFileID(), model.animation) or 2
        end
    end)
    
    -- MARCO CINEMATICO (ENCIMA DEL MODELO)
    self.frame.overlayFrame = CreateFrame("Frame", nil, self.frame)
    self.frame.overlayFrame:SetAllPoints(self.frame)
    self.frame.overlayFrame:SetFrameLevel(self.frame:GetFrameLevel() + 10)
    self.frame.overlayFrame:EnableMouse(false) -- Ignorar clicks para que pasen al modelo
    
    self.frame.background = self.frame.overlayFrame:CreateTexture(nil, "OVERLAY")
    self.frame.background:SetTexture("Interface\\AddOns\\AI_VoiceOver\\Textures\\RETRATONUEVO.blp")
    self.frame.background:SetAllPoints(self.frame.overlayFrame)
    
    -- Arrastrar el modelFrame
    self.frame.modelFrame:SetScript("OnDragStart", function(f)
        f:StartMoving()
    end)
    self.frame.modelFrame:SetScript("OnDragStop", function(f)
        f:StopMovingOrSizing()
        local parentX, parentY = self.frame:GetCenter()
        local selfX, selfY = f:GetCenter()
        local offsetX = selfX - parentX
        local offsetY = selfY - parentY
        Addon.db.profile.AlternativePortrait = Addon.db.profile.AlternativePortrait or {}
        Addon.db.profile.AlternativePortrait.modelPosition = { x = offsetX, y = offsetY }
    end)
    
    -- (Redimensionamiento con scroll eliminado)
    
    -- BOTON DE PAUSA (Al estilo clásico)
    self.frame.pauseButton = CreateFrame("Button", nil, self.frame.modelFrame)
    self.frame.pauseButton:SetFrameLevel(self.frame:GetFrameLevel() + 30) -- Por encima del overlay
    self.frame.pauseButton:SetAllPoints(self.frame.modelFrame)
    
    -- Background circular eliminado - no es necesario en estilo Midnight
    -- self.frame.pauseButton.background = self.frame.pauseButton:CreateTexture(nil, "BACKGROUND")
    -- self.frame.pauseButton.background:SetAllPoints()
    -- self.frame.pauseButton.background:SetTexture("Interface\\AddOns\\AI_VoiceOver\\Textures\\PortraitFrameBackground1")
    -- self.frame.pauseButton.background:SetAlpha(0.75)
    -- self.frame.pauseButton.background:Hide()

    self.frame.pauseButton:SetNormalTexture(GetAtlasTexturePath())
    self.frame.pauseButton:GetNormalTexture():ClearAllPoints()
    self.frame.pauseButton:GetNormalTexture():SetPoint("CENTER")
    self.frame.pauseButton:GetNormalTexture():SetSize(32, 32)
    
    self.frame.pauseButton:SetPushedTexture(GetAtlasTexturePath())
    self.frame.pauseButton:GetPushedTexture():ClearAllPoints()
    self.frame.pauseButton:GetPushedTexture():SetPoint("CENTER")
    self.frame.pauseButton:GetPushedTexture():SetSize(28, 28)

    function self.frame.pauseButton:Update()
        if Addon.db.char.IsPaused then
            -- No mostrar background en Midnight style
            -- self.background:Show()
            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(1)
        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.pauseButton:HookScript("OnEnter", function(self) self:GetNormalTexture():SetAlpha(1) end)
    self.frame.pauseButton:HookScript("OnLeave", function(self) self:GetNormalTexture():SetAlpha(Addon.db.char.IsPaused and 1 or 0) end)
    self.frame.pauseButton:HookScript("OnClick", function()
        PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON)
        SoundQueue:TogglePauseQueue()
    end)

    -- BOTON DE CIERRE (X) - Fijo
    self.frame.closeButton = CreateFrame("Button", nil, self.frame)
    self.frame.closeButton:SetSize(40, 40)
    self.frame.closeButton:SetPoint("CENTER", self.frame, "CENTER", 243, 60)
    self.frame.closeButton:SetFrameLevel(self.frame:GetFrameLevel() + 35)
    
    self.frame.closeButton:SetNormalTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Up")
    self.frame.closeButton:SetPushedTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Down")
    self.frame.closeButton:SetHighlightTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Highlight", "ADD")
    
    self.frame.closeButton:SetScript("OnClick", function()
        PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON)
        SoundQueue:RemoveAllSoundsFromQueue()
    end)

    -- SCRIPT DE ACTUALIZACIÓN PARA EL EFECTO DE MÁQUINA DE ESCRIBIR Y SEGMENTOS DE GOSSIP
    self.frame.gossipSegmentTimer = 0
    self.frame:SetScript("OnUpdate", function(f, elapsed)
        -- Efecto de máquina de escribir para título y descripción
        if f.targetTitle and f.charIndexTitle < strlenutf8(f.targetTitle) then
            f.nextCharTimer = (f.nextCharTimer or 0) + elapsed
            -- Velocidad para el nombre del NPC (un poco más lento)
            if f.nextCharTimer >= 0.05 then
                f.nextCharTimer = 0
                f.charIndexTitle = f.charIndexTitle + 1
                f.title:SetText(utf8sub(f.targetTitle, 1, f.charIndexTitle))
            end
        elseif f.targetDesc and f.charIndexDesc < strlenutf8(f.targetDesc) then
            f.nextCharTimer = (f.nextCharTimer or 0) + elapsed
            -- Velocidad para el título de la misión (un poco más rápido)
            if f.nextCharTimer >= 0.03 then
                f.nextCharTimer = 0
                f.charIndexDesc = f.charIndexDesc + 1
                f.description:SetText(utf8sub(f.targetDesc, 1, f.charIndexDesc))
            end
        end

        -- Actualizar segmentos de gossip sincronizados con el audio
        f.gossipSegmentTimer = f.gossipSegmentTimer + elapsed
        if f.gossipSegmentTimer >= 0.2 then -- Revisar cada 0.2 segundos
            f.gossipSegmentTimer = 0
            local soundData = SoundQueue:GetCurrentSound()
            if soundData and Enums.SoundEvent:IsGossipEvent(soundData.event) and soundData.title and #soundData.title > 132 then
                local newDesc = GetCurrentTextSegment(soundData, 132)
                if f.targetDesc ~= newDesc then
                    f.targetDesc = newDesc
                    f.charIndexDesc = 0
                    f.description:SetText("")
                end
            end
        end
    end)

    -- Ocultar por defecto
    self.frame:Hide()
    
    -- Restaurar posición guardada
    self:RestorePosition()
    self:RestoreModelPosition()
    
    -- Aplicar escala guardada
    if Addon.db.profile.AlternativePortrait and Addon.db.profile.AlternativePortrait.frameScale then
        self.frame:SetScale(Addon.db.profile.AlternativePortrait.frameScale)
    end
    
    -- Aplicar estado del logo (visible por defecto)
    if Addon.db.profile.AlternativePortrait and Addon.db.profile.AlternativePortrait.showLogo == false then
        if self.frame.logoFrame then
            self.frame.logoFrame:Hide()
        end
    end
end

function AlternativePortrait:RestoreModelPosition()
    if not self.frame then return end
    
    if Addon.db and Addon.db.profile and Addon.db.profile.AlternativePortrait then
        local config = Addon.db.profile.AlternativePortrait
        
        -- Restaurar posición del modelo
        if self.frame.modelFrame and config.modelPosition then
            self.frame.modelFrame:ClearAllPoints()
            self.frame.modelFrame:SetPoint("CENTER", self.frame, "CENTER", config.modelPosition.x, config.modelPosition.y)
        end
        
        -- Restaurar tamaño del modelo
        if self.frame.modelFrame and config.modelSize then
            self.frame.modelFrame:SetSize(config.modelSize, config.modelSize)
        end
        
        -- Restaurar posición del título (NPC name)
        if self.frame.titleFrame and config.titlePosition then
            self.frame.titleFrame:ClearAllPoints()
            self.frame.titleFrame:SetPoint("LEFT", self.frame, "CENTER", config.titlePosition.x, config.titlePosition.y)
        end
        
        -- Restaurar posición de la descripción (quest title)
        if self.frame.descFrame and config.descPosition then
            self.frame.descFrame:ClearAllPoints()
            self.frame.descFrame:SetPoint("LEFT", self.frame, "CENTER", config.descPosition.x, config.descPosition.y)
        end
        
        -- Restaurar posición del icono
        if self.frame.iconFrame and config.iconPosition then
            self.frame.iconFrame:ClearAllPoints()
            self.frame.iconFrame:SetPoint("CENTER", self.frame, "CENTER", config.iconPosition.x, config.iconPosition.y)
        end
        
        -- Restaurar posición del logo
        if self.frame.logoFrame and config.logoPosition then
            self.frame.logoFrame:ClearAllPoints()
            self.frame.logoFrame:SetPoint("CENTER", self.frame, "CENTER", config.logoPosition.x, config.logoPosition.y)
        end
        
        -- Restaurar posición del fondo
        if self.frame.bgFrame and config.bgPosition then
            self.frame.bgFrame:ClearAllPoints()
            self.frame.bgFrame:SetPoint("CENTER", self.frame, "CENTER", config.bgPosition.x, config.bgPosition.y)
        end
        
        -- Restaurar tamaño del fondo
        if self.frame.bgFrame and config.bgSize then
            self.frame.bgFrame:SetSize(config.bgSize, config.bgSize)
        end
    end
end

function AlternativePortrait:RestorePosition()
    if Addon.db and Addon.db.profile and Addon.db.profile.AlternativePortrait and Addon.db.profile.AlternativePortrait.position then
        local pos = Addon.db.profile.AlternativePortrait.position
        self.frame:ClearAllPoints()
        self.frame:SetPoint(pos.point, UIParent, pos.relPoint, pos.x, pos.y)
    end
end

function AlternativePortrait:Show()
    if Addon.db.profile.SoundQueueUI.PortraitStyle == 1 then
        self.frame:Show()
    end
end

function AlternativePortrait:Hide()
    self.frame:Hide()

    -- Notificar al RareTruePortrait que puede mostrarse ahora
    if RareTruePortrait and RareTruePortrait.OnAlternativePortraitHidden then
        RareTruePortrait:OnAlternativePortraitHidden()
    end
end

function AlternativePortrait:IsShown()
    return self.frame:IsShown()
end

-- Actualizar con datos del soundData actual
function AlternativePortrait:Update(soundData)
    if not soundData then return end

    -- Solo reiniciar el efecto de máquina de escribir si el texto cambió
    local newTitle = soundData.name or ""
    local newDesc = soundData.title or ""

    -- Limitar el título a 29 caracteres (consistente con RareTruePortrait)
    if strlenutf8(newTitle) > 29 then
        newTitle = utf8sub(newTitle, 1, 29) .. "..."
    end

    -- Para gossips, mostrar el segmento actual sincronizado con el audio
    if Enums.SoundEvent:IsGossipEvent(soundData.event) and newDesc then
        newDesc = GetCurrentTextSegment(soundData, 132)
    end

    if self.frame.targetTitle ~= newTitle then
        self.frame.targetTitle = newTitle
        self.frame.charIndexTitle = 0
        self.frame.title:SetText("")
    end

    if self.frame.targetDesc ~= newDesc then
        self.frame.targetDesc = newDesc
        self.frame.charIndexDesc = 0
        self.frame.description:SetText("")
    end

    self.frame.nextCharTimer = 0
    
    -- Limpiar y ocultar iconos de loot anteriores
    if self.frame.lootFrame then
        self.frame.lootFrame:Hide()
        for _, icon in ipairs(self.frame.lootIcons) do
            icon:Hide()
        end
    end
    
    -- Mostrar iconos de loot si es un rare con lootItems
    if soundData.lootItems and #soundData.lootItems > 0 then
        self:UpdateLootIcons(soundData.lootItems)
    end
    
    -- Actualizar el icono de misión según el evento
    if Enums.SoundEvent:IsQuestEvent(soundData.event) then
        self.frame.iconFrame:Show()
        if soundData.event == Enums.SoundEvent.QuestComplete then
            self.frame.questIcon:SetTexture("Interface\\AddOns\\AI_VoiceOver\\Textures\\quest-campaign-turnin.blp")
        else
            self.frame.questIcon:SetTexture("Interface\\AddOns\\AI_VoiceOver\\Textures\\quest-campaign-available.blp")
        end
    else
        self.frame.iconFrame:Hide()
    end
    
    -- Posiciones especiales para Gossip, Items, Dialogues y GameObjects
    local itemDefaults = AlternativePortraitDefaults.itemsAndDialogues
    local normalDefaults = AlternativePortraitDefaults.normal
    
    if soundData.event == Enums.SoundEvent.ItemReceived or soundData.event == Enums.SoundEvent.Dialogue or soundData.event == Enums.SoundEvent.Gossip or soundData.event == Enums.SoundEvent.GameObject or soundData.iconID then
        -- Posiciones para items según configuración del usuario
        self.frame.titleFrame:ClearAllPoints()
        self.frame.titleFrame:SetPoint("LEFT", self.frame, "CENTER", itemDefaults.title.x, itemDefaults.title.y)
        self.frame.descFrame:ClearAllPoints()
        self.frame.descFrame:SetPoint("LEFT", self.frame, "CENTER", itemDefaults.desc.x, itemDefaults.desc.y)
    else
        -- Restaurar posiciones normales para otros eventos
        local config = Addon.db.profile.AlternativePortrait
        if config and config.titlePosition then
            self.frame.titleFrame:ClearAllPoints()
            self.frame.titleFrame:SetPoint("LEFT", self.frame, "CENTER", config.titlePosition.x, config.titlePosition.y)
        else
            self.frame.titleFrame:ClearAllPoints()
            self.frame.titleFrame:SetPoint("LEFT", self.frame, "CENTER", normalDefaults.title.x, normalDefaults.title.y)
        end
        if config and config.descPosition then
            self.frame.descFrame:ClearAllPoints()
            self.frame.descFrame:SetPoint("LEFT", self.frame, "CENTER", config.descPosition.x, config.descPosition.y)
        else
            self.frame.descFrame:ClearAllPoints()
            self.frame.descFrame:SetPoint("LEFT", self.frame, "CENTER", normalDefaults.desc.x, normalDefaults.desc.y)
        end
    end
    
    -- Actualizar modelo 3D o Icono
    if self.frame.model then
        local model = self.frame.model

        -- Si hay iconID, mostrar textura en lugar de modelo 3D
        if soundData.iconID then
            model:Hide()
            if not self.frame.iconTexture then
                self.frame.iconTexture = self.frame.modelFrame:CreateTexture(nil, "ARTWORK")
                self.frame.iconTexture:SetAllPoints()
                self.frame.iconTexture:SetTexCoord(0.08, 0.92, 0.08, 0.92) -- Zoom ligero para quitar bordes
            end
            self.frame.iconTexture:SetTexture(soundData.iconID)
            self.frame.iconTexture:Show()
        else
            if self.frame.iconTexture then
                self.frame.iconTexture:Hide()
            end
            model:Show()

            -- Detectar si debe mostrar el narrador (misma lógica que ShouldShowBookFor del portrait clásico)
            local shouldShowBook = false
            if soundData.gameObjectID or soundData.isGameObject then
                shouldShowBook = true
            elseif not soundData.displayID and not soundData.creatureID then
                local guid = soundData.unitGUID
                local guidType = guid and Utils:GetGUIDType(guid)
                if not guidType then
                    shouldShowBook = true
                elseif guidType == Enums.GUID.Item or guidType == Enums.GUID.GameObject then
                    shouldShowBook = true
                end
            end

            -- DisplayID por nombre de NPC — resuelve NPCs que Blizzard actua con
            -- criaturas base distintas. soundData.name contiene el nombre correcto.
            local displayIDByName = {
                ["Arator"] = 124777,
            }
            if not soundData.displayID and soundData.name and displayIDByName[soundData.name] then
                soundData.displayID = displayIDByName[soundData.name]
            end

            if shouldShowBook then
                -- Mostrar narrador/libro
                if model.oldCreatureID ~= 15608 then
                    model:ClearModel()
                    model:SetCreature(15608)
                    model.oldCreatureID = 15608
                    model.oldDisplayID = nil
                end
            elseif soundData.displayID then
                local displayID = soundData.displayID
                if displayID ~= model.oldDisplayID then
                    model:ClearModel()
                    model:SetDisplayInfo(displayID)
                    model.oldDisplayID = displayID
                    model.oldCreatureID = nil
                end
            else
                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 ~= model.oldCreatureID then
                    model:ClearModel()
                    model:SetCreature(creatureID)
                    model.oldCreatureID = creatureID
                    model.oldDisplayID = nil
                end
            end
        end

        -- La configuración de cámara se maneja en el OnUpdate del modelo
        -- Animación de hablar
        model:SetAnimation(60)
    end
end

-- Función para crear/actualizar iconos de loot
function AlternativePortrait:UpdateLootIcons(lootItems)
    if not self.frame or not self.frame.lootFrame then return end
    
    local ICON_SIZE = 36
    local ICON_SPACING = 4
    local MAX_ICONS_PER_ROW = 6
    
    -- Ocultar iconos existentes
    for _, icon in ipairs(self.frame.lootIcons) do
        icon:Hide()
    end
    
    -- Crear/actualizar iconos
    for i, itemID in ipairs(lootItems) do
        local icon = self.frame.lootIcons[i]
        
        if not icon then
            -- Crear nuevo icono
            icon = CreateFrame("Button", nil, self.frame.lootFrame)
            icon:SetSize(ICON_SIZE, ICON_SIZE)
            icon:SetFrameLevel(self.frame.lootFrame:GetFrameLevel() + 1)
            
            icon.texture = icon:CreateTexture(nil, "ARTWORK")
            icon.texture:SetAllPoints()
            icon.texture:SetTexCoord(0.08, 0.92, 0.08, 0.92) -- Quitar bordes
            
            -- Borde del icono
            icon.border = icon:CreateTexture(nil, "OVERLAY")
            icon.border:SetPoint("TOPLEFT", icon, "TOPLEFT", -16, 16)
            icon.border:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 16, -16)
            icon.border:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
            icon.border:SetBlendMode("ADD")
            icon.border:SetAlpha(0.8)
            
            -- Glow para items épicos
            icon.glow = icon:CreateTexture(nil, "BACKGROUND")
            icon.glow:SetPoint("TOPLEFT", icon, "TOPLEFT", -20, 20)
            icon.glow:SetPoint("BOTTOMRIGHT", icon, "BOTTOMRIGHT", 20, -20)
            icon.glow:SetTexture("Interface\\SpellActivationOverlay\\IconAlert")
            icon.glow:SetTexCoord(0.00781250, 0.50781250, 0.27734375, 0.52734375)
            icon.glow:SetBlendMode("ADD")
            icon.glow:SetAlpha(0)
            icon.glow:Hide()
            
            -- Texto de precio para items grises
            icon.priceText = icon:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            icon.priceText:SetPoint("BOTTOM", icon, "BOTTOM", 0, -12)
            icon.priceText:SetFont("Fonts\\FRIZQT__.TTF", 10, "OUTLINE")
            icon.priceText:SetTextColor(1, 1, 1)
            icon.priceText:Hide()
            
            -- Tooltip al pasar el mouse
            icon:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:SetItemByID(self.itemID)
                GameTooltip:Show()
            end)
            
            icon:SetScript("OnLeave", function(self)
                GameTooltip:Hide()
            end)
            
            -- Click para ver item en el chat
            icon:SetScript("OnClick", function(self)
                local _, itemLink = GetItemInfo(self.itemID)
                if itemLink then
                    ChatFrame1:AddMessage(itemLink)
                end
            end)
            
            self.frame.lootIcons[i] = icon
        end
        
        -- Posicionar icono
        local row = math.floor((i - 1) / MAX_ICONS_PER_ROW)
        local col = (i - 1) % MAX_ICONS_PER_ROW
        icon:ClearAllPoints()
        icon:SetPoint("TOPLEFT", self.frame.lootFrame, "TOPLEFT", col * (ICON_SIZE + ICON_SPACING), -row * (ICON_SIZE + ICON_SPACING))
        
        -- Asignar item ID
        icon.itemID = itemID
        
        -- Obtener icono del item
        local itemIcon = GetItemIcon(itemID)
        if itemIcon then
            icon.texture:SetTexture(itemIcon)
        else
            icon.texture:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
            -- Intentar cargar el item para obtener el icono
            local item = Item:CreateFromItemID(itemID)
            item:ContinueOnItemLoad(function()
                local loadedIcon = GetItemIcon(itemID)
                if loadedIcon then
                    icon.texture:SetTexture(loadedIcon)
                end
            end)
        end
        
        -- Colorear borde según calidad del item y efectos especiales
        icon.glow:Hide()
        icon.glow:SetAlpha(0)
        icon.priceText:Hide()
        
        local _, _, itemRarity, _, _, _, _, _, _, _, sellPrice = GetItemInfo(itemID)
        if itemRarity then
            local r, g, b = GetItemQualityColor(itemRarity)
            icon.border:SetVertexColor(r, g, b)
            
            -- Items grises y verdes - mostrar precio de venta con iconos de monedas
            if (itemRarity == 0 or itemRarity == 2) and sellPrice and sellPrice > 0 then
                local gold = math.floor(sellPrice / 10000)
                local silver = math.floor((sellPrice % 10000) / 100)
                local copper = sellPrice % 100
                local priceStr = ""
                if gold > 0 then
                    priceStr = gold .. "|TInterface\\MoneyFrame\\UI-GoldIcon:0|t"
                elseif silver > 0 then
                    priceStr = silver .. "|TInterface\\MoneyFrame\\UI-SilverIcon:0|t"
                else
                    priceStr = copper .. "|TInterface\\MoneyFrame\\UI-CopperIcon:0|t"
                end
                icon.priceText:SetText(priceStr)
                icon.priceText:Show()
            end
            
            -- Items épicos - efecto glow
            if itemRarity >= 4 then
                icon.glow:SetVertexColor(r, g, b)
                icon.glow:SetAlpha(0.8)
                icon.glow:Show()
            end
        else
            icon.border:SetVertexColor(1, 1, 1)
            -- Intentar obtener calidad cuando el item cargue
            local item = Item:CreateFromItemID(itemID)
            item:ContinueOnItemLoad(function()
                local _, _, loadedRarity, _, _, _, _, _, _, _, loadedSellPrice = GetItemInfo(itemID)
                if loadedRarity then
                    local r, g, b = GetItemQualityColor(loadedRarity)
                    icon.border:SetVertexColor(r, g, b)
                    
                    -- Items grises y verdes - mostrar precio con iconos de monedas
                    if (loadedRarity == 0 or loadedRarity == 2) and loadedSellPrice and loadedSellPrice > 0 then
                        local gold = math.floor(loadedSellPrice / 10000)
                        local silver = math.floor((loadedSellPrice % 10000) / 100)
                        local copper = loadedSellPrice % 100
                        local priceStr = ""
                        if gold > 0 then
                            priceStr = gold .. "|TInterface\\MoneyFrame\\UI-GoldIcon:0|t"
                        elseif silver > 0 then
                            priceStr = silver .. "|TInterface\\MoneyFrame\\UI-SilverIcon:0|t"
                        else
                            priceStr = copper .. "|TInterface\\MoneyFrame\\UI-CopperIcon:0|t"
                        end
                        icon.priceText:SetText(priceStr)
                        icon.priceText:Show()
                    end
                    
                    -- Items épicos - efecto glow
                    if loadedRarity >= 4 then
                        icon.glow:SetVertexColor(r, g, b)
                        icon.glow:SetAlpha(0.8)
                        icon.glow:Show()
                    end
                end
            end)
        end
        
        icon:Show()
    end
    
    -- Mostrar el frame de loot
    self.frame.lootFrame:Show()
end

function AlternativePortrait:RefreshConfig()
    if not self.frame then return end
    
    -- Verificar si debe mostrarse
    local useAlternative = Addon.db.profile.SoundQueueUI.PortraitStyle == 1
    
    if useAlternative then
        -- Si hay audio reproduciéndose, mostrar
        if SoundQueue and SoundQueue:GetCurrentSound() then
            self:Show()
            self:Update(SoundQueue:GetCurrentSound())
        end
        self:UpdatePauseDisplay()
    else
        self:Hide()
    end
end

function AlternativePortrait:UpdatePauseDisplay()
    if self.frame and self.frame.pauseButton then
        self.frame.pauseButton:Update()
    end
end
