setfenv(1, VoiceOver)
DialogueAdapter = {}

-- ============================================================
-- NPCs BLOQUEADOS — Sus diálogos de chat NO se reproducirán,
-- aunque existan audios. El juego ya tiene sus voces originales.
-- Para agregar más: ["Nombre exacto"] = true,
-- ============================================================
local blockedNPCNames = {
    ["Arator"]                  = true,
    ["Alonsus Faol"]            = true,
    ["Danath Aterratrols"]      = true,
    ["Kurdran Martillo Salvaje"] = true,
    ["Lor'themar Theron"]       = true,
    ["Gran Magister Rommath"]   = true,
    ["Zul'jarra"]               = true,
    ["Zul'jan"]                 = true,
    ["Lady Liadrin"]            = true,
    ["Lilaju"]                  = true,
    ["Orweyna"]                  = true,
    ["Hagar"]                  = true,
}

local function IsNPCNameBlocked(name)
    return name ~= nil and blockedNPCNames[name] == true
end

local frame = CreateFrame("Frame")

-- Registrar eventos de chat de NPCs
frame:RegisterEvent("CHAT_MSG_MONSTER_SAY")
frame:RegisterEvent("CHAT_MSG_MONSTER_YELL")
frame:RegisterEvent("CHAT_MSG_MONSTER_EMOTE")
frame:RegisterEvent("CHAT_MSG_MONSTER_WHISPER")

-- ============== DIÁLOGOS ESPECIALES UNIVERSALES ==============
-- Estos diálogos se reproducen sin importar el nombre del NPC emisor
-- Útil para mensajes del sistema que usan el nombre del jugador como emisor
local universalDialogues = {
    ["Percibes un glifo de cielonáutica cerca."] = {
        fileName = "Cielonautica",
        event = Enums.SoundEvent.Misc,
        duration = 2.2,
        displayID = nil,
        iconID = 5926319,
    },
}

-- ============== RANDOM QUOTES POOL TRACKING ==============
-- Tabla para rastrear qué quotes ya se han usado por NPC (evita repeticiones)
local usedRandomQuotes = {}

-- ============================================================
-- AYUDANTE: GÉNERO DEL NPC
-- ============================================================

---Extrae el ID numérico de un GUID de NPC
---@param guid string|nil
---@return number|nil
local function GetNPCIDFromGUID(guid)
    if not guid then return nil end
    local unitType, _, _, _, _, npcID = strsplit("-", guid)
    if unitType == "Creature" or unitType == "Vehicle" then
        return tonumber(npcID)
    end
    return nil
end

---Obtiene el género de un NPC de forma estricta
---@param npcName string Nombre del NPC
---@param guid string|nil GUID del NPC
---@return number gender 1=Unknown, 2=Male, 3=Female
local function GetNPCGender(npcName, guid)
    -- 1. Intentar obtener unidad directa por GUID (Si el juego la tiene mapeada)
    if guid then
        local unit = UnitTokenFromGUID(guid)
        if unit then
            local sex = UnitSex(unit)
            if sex and sex > 1 then return sex end
        end

        -- Comparar GUID con unidades conocidas
        if UnitGUID("target") == guid then return UnitSex("target") end
        if UnitGUID("mouseover") == guid then return UnitSex("mouseover") end
        if UnitGUID("focus") == guid then return UnitSex("focus") end
        if UnitGUID("softtarget") == guid then return UnitSex("softtarget") end

        -- Escaneo de Nameplates por GUID
        for i = 1, 40 do
            local plateUnit = "nameplate"..i
            if UnitExists(plateUnit) and UnitGUID(plateUnit) == guid then
                return UnitSex(plateUnit)
            end
        end
    end

    -- 2. FALLBACK: Buscar por NOMBRE (Como en NPCDialogosChat)
    -- Esto ayuda si el GUID falla o no se proporciona
    if npcName and npcName ~= "" then
        -- Primero target (lo más probable si el usuario está interactuando)
        if UnitName("target") == npcName then return UnitSex("target") end
        
        -- Luego mouseover y focus
        if UnitName("mouseover") == npcName then return UnitSex("mouseover") end
        if UnitName("focus") == npcName then return UnitSex("focus") end
        
        -- Finalmente escanear placas de nombre por nombre
        for i = 1, 40 do
            local plateUnit = "nameplate"..i
            if UnitExists(plateUnit) and UnitName(plateUnit) == npcName then
                return UnitSex(plateUnit)
            end
        end
    end

    -- 3. Si nada de lo anterior funcionó, es realmente Desconocido
    return 1
end

---Verifica si un archivo de audio coincide con el género
---@param audioFile string Nombre del archivo (ej: "m-npc_random1")
---@param npcGender number 1=Any, 2=Male, 3=Female
---@return boolean matches
local function AudioMatchesGender(audioFile, npcGender)
    if not audioFile then return false end
    
    local isMalePrefix = audioFile:sub(1, 2) == "m-"
    local isFemalePrefix = audioFile:sub(1, 2) == "f-"
    
    -- SI EL GÉNERO ES DESCONOCIDO, NO REPRODUCIR ARCHIVOS CON PREFIJO (ESTRICTO)
    if npcGender == 1 then
        return not (isMalePrefix or isFemalePrefix)
    end
    
    -- Si es un archivo masculino, el NPC DEBE ser macho
    if isMalePrefix then
        return npcGender == 2
    end
    
    -- Si es un archivo femenino, el NPC DEBE ser hembra
    if isFemalePrefix then
        return npcGender == 3
    end
    
    -- Archivos sin prefijo se consideran neutrales
    return true
end

-- ============================================================
-- SISTEMA 1: DIALOGUE LOOKUP (Diálogos con hash específico)
-- Este sistema busca diálogos exactos en DialogueLookupByNPCName
-- NO tiene fallback a ningún otro sistema
-- ============================================================

local function FindDialogueHash(npcName, dialogueText)
    -- Limpiar el texto (remover saltos de línea como en el backend)
    local cleanText = dialogueText:gsub("\n", " "):gsub("\r", " ")
    
    -- Obtener el nombre del jugador para reemplazarlo
    local playerName = UnitName("player")

    -- Buscar en todos los módulos de datos registrados
    for _, module in ipairs(DataModules.registeredModulesOrdered) do
        if module.DialogueLookupByNPCName and module.DialogueLookupByNPCName[npcName] then
            local npcData = module.DialogueLookupByNPCName[npcName]
            local npcDisplayID = npcData.displayID

            -- Buscar por coincidencia de texto
            for hash, data in pairs(npcData) do
                if hash ~= "displayID" and type(data) == "table" then
                    local dataText = data.text:gsub("\n", " "):gsub("\r", " ")

                    -- MÉTODO 1: Coincidencia exacta
                    if cleanText == dataText then
                        return hash, data.displayID or npcDisplayID
                    end

                    -- MÉTODO 2: Comparación después de coma (para nombres de jugador variables)
                    local gameTextAfterComma = cleanText:match(",%s*(.+)")
                    local dataTextAfterComma = dataText:match(",%s*(.+)")
                    if gameTextAfterComma and dataTextAfterComma and gameTextAfterComma == dataTextAfterComma then
                        return hash, data.displayID or npcDisplayID
                    end

                    -- MÉTODO 3: Búsqueda parcial (uno contiene al otro)
                    if cleanText:find(dataText, 1, true) or dataText:find(cleanText, 1, true) then
                        return hash, data.displayID or npcDisplayID
                    end
                    
                    -- MÉTODO 4: Reemplazar nombre del jugador por "Adalid" y comparar
                    if playerName and playerName ~= "" then
                        local normalizedGameText = cleanText:gsub(playerName, "Adalid")
                        if normalizedGameText == dataText then
                            return hash, data.displayID or npcDisplayID
                        end
                        -- También intentar comparación parcial con texto normalizado
                        if normalizedGameText:find(dataText, 1, true) or dataText:find(normalizedGameText, 1, true) then
                            return hash, data.displayID or npcDisplayID
                        end
                    end
                end
            end
        end
    end

    return nil, nil
end

-- ============================================================
-- SISTEMA 2: RANDOM QUOTES POOL (Quotes aleatorias para NPCs específicos)
-- Este sistema es INDEPENDIENTE de DialogueLookup
-- SOLO funciona para NPCs que están EXPLÍCITAMENTE en RandomQuotesPool
-- ============================================================

---Verifica si un NPC tiene RandomQuotesPool disponible
---@param npcName string Nombre del NPC
---@return boolean hasPool
local function NPCHasRandomQuotesPool(npcName)
    for _, module in ipairs(DataModules.registeredModulesOrdered) do
        if module.RandomQuotesPool and module.RandomQuotesPool[npcName] then
            Debug:Print("NPCHasRandomQuotesPool: SI tiene pool -> " .. tostring(npcName), "RQ-DEBUG")
            return true
        end
    end
    Debug:Print("NPCHasRandomQuotesPool: NO tiene pool -> " .. tostring(npcName), "RQ-DEBUG")
    return false
end
---Extrae las palabras de un texto (sin signos de puntuación)
---@param text string
---@return table palabras Lista de palabras
local function ExtractWords(text)
    local words = {}
    -- Normalizar comillas curvas Unicode a recta ASCII antes de limpiar
    local cleanText = text:gsub("\xe2\x80\x98", "'"):gsub("\xe2\x80\x99", "'")
    -- Remover signos de puntuación y extraer solo palabras
    cleanText = cleanText:gsub("[%p%c]", " "):lower()
    for word in cleanText:gmatch("%S+") do
        if #word > 0 then
            table.insert(words, word)
        end
    end
    return words
end

---Cuenta cuántas palabras coinciden entre dos listas
---@param words1 table Lista de palabras 1
---@param words2 table Lista de palabras 2
---@return number matchCount Cantidad de palabras que coinciden
---@return number totalWords Total de palabras únicas combinadas
local function CountMatchingWords(words1, words2)
    local set1 = {}
    local set2 = {}
    
    for _, word in ipairs(words1) do
        set1[word] = true
    end
    for _, word in ipairs(words2) do
        set2[word] = true
    end
    
    local matchCount = 0
    local allWords = {}
    
    for word in pairs(set1) do
        allWords[word] = true
        if set2[word] then
            matchCount = matchCount + 1
        end
    end
    for word in pairs(set2) do
        allWords[word] = true
    end
    
    local totalWords = 0
    for _ in pairs(allWords) do
        totalWords = totalWords + 1
    end
    
    return matchCount, totalWords
end

---Busca un match en RandomQuotesPool basado en palabras (no orden exacto)
---@param npcName string Nombre del NPC
---@param gameText string Texto del diálogo del juego
---@param npcGender number Género detectado (1=Any, 2=M, 3=F)
---@return string|nil fileName, number|nil displayID
local function FindExactQuoteInPool(npcName, gameText, npcGender)
    -- Extraer palabras del texto del juego
    local gameWords = ExtractWords(gameText)
    Debug:Print("FindExactQuoteInPool: NPC='" .. tostring(npcName) .. "' genero=" .. tostring(npcGender) .. " texto='" .. tostring(gameText) .. "'", "RQ-DEBUG")
    Debug:Print("  Palabras extraidas del juego: " .. tostring(#gameWords), "RQ-DEBUG")

    if #gameWords == 0 then
        Debug:Print("  Sin palabras, retornando nil", "RQ-DEBUG")
        return nil, nil
    end

    local bestMatch = nil
    local bestMatchScore = 0
    local bestDisplayID = nil

    for _, module in ipairs(DataModules.registeredModulesOrdered) do
        if module.RandomQuotesPool and module.RandomQuotesPool[npcName] then
            local poolData = module.RandomQuotesPool[npcName]
            local quotes = poolData.quotes

            -- Obtener DisplayID por género desde la tabla genders (Prioridad)
            local displayID = nil
            if poolData.genders and poolData.genders[npcGender] then
                displayID = poolData.genders[npcGender].displayID
            end

            -- Fallback al displayID general
            if not displayID then
                displayID = poolData.displayID
            end

            if quotes then
                for _, quoteData in ipairs(quotes) do
                    if type(quoteData) == "table" and quoteData.text then
                        -- FILTRAR POR GÉNERO
                        if AudioMatchesGender(quoteData.audioFile, npcGender) then
                            -- Extraer palabras de la quote
                            local quoteWords = ExtractWords(quoteData.text)

                            if #quoteWords > 0 then
                                local matchCount, totalWords = CountMatchingWords(gameWords, quoteWords)

                                -- Calcular porcentaje de match (basado en palabras coincidentes)
                                local matchPercent = matchCount / totalWords
                                Debug:Print("  Comparando con pool texto='" .. tostring(quoteData.text) .. "' audio='" .. tostring(quoteData.audioFile) .. "' match=" .. string.format("%.0f%%", matchPercent * 100), "RQ-DEBUG")

                                -- Si coinciden 25% o más de las palabras, considerarlo un match
                                if matchPercent >= 0.25 then
                                    -- Preferir el match con mayor porcentaje
                                    if matchPercent > bestMatchScore then
                                        bestMatch = quoteData.audioFile
                                        bestMatchScore = matchPercent
                                        bestDisplayID = displayID
                                        Debug:Print("  >> Nuevo mejor match: '" .. tostring(bestMatch) .. "' (" .. string.format("%.0f%%", bestMatchScore * 100) .. ")", "RQ-DEBUG")
                                    end
                                end
                            end
                        else
                            Debug:Print("  Ignorado por genero: audio='" .. tostring(quoteData.audioFile) .. "' generoNPC=" .. tostring(npcGender), "RQ-DEBUG")
                        end
                    end
                end
            end
        end
    end

    if bestMatch then
        Debug:Print("FindExactQuoteInPool RESULTADO: '" .. tostring(bestMatch) .. "' score=" .. string.format("%.0f%%", bestMatchScore * 100), "RQ-DEBUG")
    else
        Debug:Print("FindExactQuoteInPool: sin match suficiente, usara random", "RQ-DEBUG")
    end

    return bestMatch, bestDisplayID
end

---Obtiene una quote ALEATORIA del pool (SIN intentar hacer match)
---@param npcName string Nombre del NPC
---@param npcGender number Género detectado
---@return string|nil fileName, number|nil displayID
local function GetRandomQuoteFromPool(npcName, npcGender)
    Debug:Print("GetRandomQuoteFromPool: NPC='" .. tostring(npcName) .. "' genero=" .. tostring(npcGender), "RQ-DEBUG")
    for _, module in ipairs(DataModules.registeredModulesOrdered) do
        if module.RandomQuotesPool and module.RandomQuotesPool[npcName] then
            local poolData = module.RandomQuotesPool[npcName]
            local quotes = poolData.quotes
            
            -- Obtener DisplayID por género
            local displayID = nil
            if poolData.genders and poolData.genders[npcGender] then
                displayID = poolData.genders[npcGender].displayID
            end
            
            if not displayID then
                displayID = poolData.displayID
            end
            
            if not quotes or #quotes == 0 then
                return nil, nil
            end
            
            -- Inicializar tracking si no existe
            if not usedRandomQuotes[npcName] then
                usedRandomQuotes[npcName] = {}
            end
            
            -- Resetear si ya usamos todas
            if #usedRandomQuotes[npcName] >= #quotes then
                usedRandomQuotes[npcName] = {}
            end
            
            -- Buscar quotes disponibles (no usadas) y que coincidan con el género
            local available = {}
            for i, quoteData in ipairs(quotes) do
                local audioFile = type(quoteData) == "table" and quoteData.audioFile or quoteData
                
                -- Verificar Género y Uso previo
                if audioFile and AudioMatchesGender(audioFile, npcGender) then
                    local used = false
                    for _, usedIdx in ipairs(usedRandomQuotes[npcName]) do
                        if usedIdx == i then used = true; break end
                    end
                    if not used then
                        table.insert(available, {idx = i, file = audioFile})
                    end
                end
            end
            
            -- Seleccionar aleatoria
            if #available > 0 then
                local pick = available[math.random(#available)]
                table.insert(usedRandomQuotes[npcName], pick.idx)
                Debug:Print("GetRandomQuoteFromPool RESULTADO: '" .. tostring(pick.file) .. "' (disponibles=" .. tostring(#available) .. ")", "RQ-DEBUG")
                return pick.file, displayID
            else
                Debug:Print("GetRandomQuoteFromPool: sin quotes disponibles para genero=" .. tostring(npcGender), "RQ-DEBUG")
            end
        end
    end

    Debug:Print("GetRandomQuoteFromPool: no encontro pool para '" .. tostring(npcName) .. "'", "RQ-DEBUG")
    return nil, nil
end

-- ============================================================
-- FUNCIÓN AUXILIAR: Obtener NPC ID por nombre
-- ============================================================

local function FindNPCIDByName(npcName)
    for _, module in ipairs(DataModules.registeredModulesOrdered) do
        if module.NPCNameLookupByNPCID then
            for npcID, name in pairs(module.NPCNameLookupByNPCID) do
                if name == npcName then
                    return npcID
                end
            end
        end
    end
    return nil
end

-- ============================================================
-- FUNCIÓN PRINCIPAL: Procesar diálogo
-- Las lógicas están COMPLETAMENTE SEPARADAS:
-- 1. Primero verifica diálogos universales
-- 2. Luego busca en DialogueLookup (sin fallback)
-- 3. Si NO está en DialogueLookup, verifica si el NPC tiene RandomQuotesPool
-- 4. Si no encuentra en ningún lado, NO reproduce nada
-- ============================================================

---Procesar diálogo
---@param dialogueText string Texto del chat
---@param npcName string Nombre del emisor
---@param guid string|nil GUID del emisor
local function ProcessDialogue(dialogueText, npcName, guid)
    local cleanText = dialogueText:gsub("\n", " "):gsub("\r", " ")
    Debug:Print("ProcessDialogue: NPC='" .. tostring(npcName) .. "' texto='" .. tostring(cleanText) .. "'", "RQ-DEBUG")

    -- ========== PASO 1: VERIFICAR DIÁLOGOS UNIVERSALES ==========
    -- Se verifica antes de npcName porque estos mensajes pueden venir del sistema sin sender
    local universalMatch = universalDialogues[cleanText]
    if universalMatch then
        local soundData = {
            event = universalMatch.event,
            name = npcName or "",
            text = dialogueText,
            title = dialogueText,
            fileName = universalMatch.fileName,
            unitGUID = nil,
            displayID = universalMatch.displayID,
            iconID = universalMatch.iconID,
        }

        SoundQueue:AddSoundToQueue(soundData)
        return
    end

    if not npcName or npcName == "" then
        return
    end

    -- ========== VERIFICAR BLOQUEO POR NOMBRE ==========
    if IsNPCNameBlocked(npcName) then
        return
    end

    local dialogueHash = nil
    local displayID = nil
    local isRandomQuote = false

    -- ========== PASO 2: BUSCAR EN DIALOGUE LOOKUP (Sistema 1) ==========
    dialogueHash, displayID = FindDialogueHash(npcName, dialogueText)
    
    if not dialogueHash then
        -- ========== PASO 3: VERIFICAR SI EL NPC TIENE RANDOM QUOTES POOL ==========
        if NPCHasRandomQuotesPool(npcName) then
            -- Detectar género
            local npcGender = GetNPCGender(npcName, guid)

            -- Primero intentar match exacto en el pool
            dialogueHash, displayID = FindExactQuoteInPool(npcName, dialogueText, npcGender)
            
            if dialogueHash then
                isRandomQuote = true
            else
                -- Si no hay match exacto, usar quote aleatoria
                dialogueHash, displayID = GetRandomQuoteFromPool(npcName, npcGender)
                if dialogueHash then
                    isRandomQuote = true
                end
            end
        end
    end

    -- ========== PASO 4: SI NO HAY NADA, NO REPRODUCIR ==========
    if not dialogueHash then
        Debug:Print("ProcessDialogue: sin audio para NPC='" .. tostring(npcName) .. "' texto='" .. tostring(cleanText) .. "'", "RQ-DEBUG")
        return
    end

    Debug:Print("ProcessDialogue: REPRODUCIENDO fileName='" .. tostring(dialogueHash) .. "' displayID=" .. tostring(displayID) .. " isRandom=" .. tostring(isRandomQuote), "RQ-DEBUG")
    -- ========== PASO 5: CREAR Y ENCOLAR EL SONIDO ==========
    local npcID = FindNPCIDByName(npcName)
    local unitGUID = nil
    
    if npcID then
        unitGUID = Utils:MakeGUID(Enums.GUID.Creature, npcID)
    end

    local soundData = {
        event = Enums.SoundEvent.Dialogue,
        name = npcName,
        text = dialogueText,
        title = dialogueText,
        fileName = dialogueHash,
        unitGUID = unitGUID,
        displayID = displayID,
    }

    SoundQueue:AddSoundToQueue(soundData)
end

-- ============================================================
-- EVENT HANDLER
-- ============================================================

frame:SetScript("OnEvent", function(self, event, ...)
    local message, npcName, language, channelString, target, flags, unknown, channelNumber, channelName, unknown2, counter, guid = ...
    
    -- Verificar que Addon.db esté inicializado
    if not Addon or not Addon.db or not Addon.db.profile then
        return
    end
    
    -- Desactivar en combate dentro de instancias (evita errores con secret values)
    if InCombatLockdown() then
        local inInstance, instanceType = IsInInstance()
        if inInstance and (instanceType == "party" or instanceType == "raid") then
            return
        end
    end

    if message and npcName then
        -- Usar pcall para evitar errores con secret values en WoW 12.0
        pcall(function()
            ProcessDialogue(message, npcName, guid)
        end)
    end
end)



return {
    ProcessDialogue = ProcessDialogue,
    NPCHasRandomQuotesPool = NPCHasRandomQuotesPool,
    FindExactQuoteInPool = FindExactQuoteInPool,
    GetRandomQuoteFromPool = GetRandomQuoteFromPool
}
