Mudanças entre as edições de "Módulo:Info.Skills"

De Wiki Gla
Ir para navegação Ir para pesquisar
m
m
 
Linha 205: Linha 205:


     -- Formato novo: icon~pve~pvp~cd~video~energy
     -- Formato novo: icon~pve~pvp~cd~video~energy
    -- IMPORTANTE: precisa preservar campos vazios para manter ordem fixa
     local parts = {}
     local parts = {}
     for part in str:gmatch("[^~]+") do
     -- Split manual que preserva campos vazios
         table.insert(parts, trim(part))
    local start = 1
    while true do
        local pos = str:find("~", start)
         if not pos then
            -- Último campo
            table.insert(parts, trim(str:sub(start)))
            break
        end
        table.insert(parts, trim(str:sub(start, pos - 1)))
        start = pos + 1
    end
    -- Garante que temos pelo menos 6 campos (preenche com "" se necessário)
    while #parts < 6 do
        table.insert(parts, "")
     end
     end
     -- Também aceita formato com vírgula: pve,pvp,cd,video
     -- Também aceita formato com vírgula: pve,pvp,cd,video
Linha 1 103: Linha 1 117:
                             -- E só sobrescreve se o campo do template estiver vazio ou não existir
                             -- E só sobrescreve se o campo do template estiver vazio ou não existir
                             for k, v in pairs(normalized) do
                             for k, v in pairs(normalized) do
                                 -- Para video: preserva o do template se não estiver vazio
                                 -- Para video: SEMPRE preserva o do template se existir (não faz merge)
                                 if k == "video" then
                                 if k == "video" then
                                     if (not obj.weapon.video or obj.weapon.video == "") and v and v ~= "" then
                                    -- Se o template tem vídeo, preserva (não sobrescreve com módulo)
                                     if obj.weapon.video and trim(obj.weapon.video) ~= "" then
                                        -- Mantém o vídeo do template, não faz nada
                                    elseif v and v ~= "" then
                                        -- Só usa do módulo se template não tem
                                         obj.weapon.video = v
                                         obj.weapon.video = v
                                     end
                                     end
Linha 1 438: Linha 1 456:
                             -- E só sobrescreve se o campo do template estiver vazio ou não existir
                             -- E só sobrescreve se o campo do template estiver vazio ou não existir
                             for k, v in pairs(normalized) do
                             for k, v in pairs(normalized) do
                                 -- Para video: preserva o do template se não estiver vazio
                                 -- Para video: SEMPRE preserva o do template se existir (não faz merge)
                                 if k == "video" then
                                 if k == "video" then
                                     if (not obj.weapon.video or obj.weapon.video == "") and v and v ~= "" then
                                    -- Se o template tem vídeo, preserva (não sobrescreve com módulo)
                                     if obj.weapon.video and trim(obj.weapon.video) ~= "" then
                                        -- Mantém o vídeo do template, não faz nada
                                    elseif v and v ~= "" then
                                        -- Só usa do módulo se template não tem
                                         obj.weapon.video = v
                                         obj.weapon.video = v
                                     end
                                     end

Edição atual tal como às 17h47min de 5 de dezembro de 2025

A documentação para este módulo pode ser criada em Módulo:Info.Skills/doc

-- Módulo:Info.Skills — skill(), subskill(), emote()
local p = {}

local utils = require("Módulo:Info.Utils")
local trim = utils.trim
local safeArgs = utils.safeArgs
local collectJsonObjects = utils.collectJsonObjects
local requireCharacterModule = utils.requireCharacterModule
local resolveCharFromFrames = utils.resolveCharFromFrames
local colorize = utils.colorize
local nz = utils.nz
local parseFlags = utils.parseFlags

-- ===== Cache de metadados das skills para herança dos Subskills =====
local SkillMetaStore = {}

local function currentPageKey()
    local title = mw.title.getCurrentTitle()
    return title and title.fullText or "__page__"
end

local function metaBucket(char)
    local key = currentPageKey() .. "::" .. (trim(char or "") ~= "" and trim(char) or "__char__")
    if not SkillMetaStore[key] then
        SkillMetaStore[key] = { byIndex = {}, byName = {} }
    end
    return SkillMetaStore[key]
end

local function storeSkillMeta(char, index, name, data)
    if not data then return end
    local bucket = metaBucket(char)
    if index then
        local idx = tonumber(index) or index
        bucket.byIndex[idx] = data
    end
    if name and trim(name) ~= "" then
        bucket.byName[trim(name)] = data
    end
end

local function fetchSkillMeta(char, ref)
    if not ref then return nil end
    local bucket = metaBucket(char)
    local idxNum = tonumber(ref)
    if idxNum and bucket.byIndex[idxNum] then
        return bucket.byIndex[idxNum]
    end
    if bucket.byIndex[ref] then
        return bucket.byIndex[ref]
    end
    local refName = trim(ref)
    if refName ~= "" and bucket.byName[refName] then
        return bucket.byName[refName]
    end
    return nil
end

local function inheritSubFromMeta(char, sub)
    if type(sub) ~= "table" then return sub end
    local ref = sub.refM or sub.M or sub.m or sub.name or sub.n
    if not ref or trim(ref) == "" then
        return sub
    end
    local meta = fetchSkillMeta(char, ref)
    if not meta then
        return sub
    end

    local function pick(current, fallback)
        if current == false or current == 0 or current == "0" then
            return current
        end
        if current ~= nil and trim(tostring(current)) ~= "" then
            return current
        end
        return fallback
    end

    local hydrated = {}
    for k, v in pairs(sub) do
        hydrated[k] = v
    end
    hydrated.name = pick(sub.name or sub.n, meta.name)
    hydrated.icon = pick(sub.icon, meta.icon)
    hydrated.level = pick(sub.level, meta.level)
    hydrated.video = pick(sub.video, meta.video)
    hydrated.energy = pick(sub.energy, meta.energy)
    hydrated.powerpve = pick(sub.powerpve, meta.powerpve)
    hydrated.powerpvp = pick(sub.powerpvp, meta.powerpvp)
    hydrated.cooldown = pick(sub.cooldown, meta.cooldown)

    hydrated.desc = pick(sub.desc, meta.desc)
    hydrated.descPt = pick(sub.descPt, meta.descPt)
    hydrated.descEn = pick(sub.descEn, meta.descEn)
    hydrated.descEs = pick(sub.descEs, meta.descEs)
    hydrated.descPl = pick(sub.descPl, meta.descPl)

    -- PRESERVA weapon explicitamente (não herda de meta)
    if sub.weapon ~= nil then
        hydrated.weapon = sub.weapon
    end
    if sub.weaponPacked ~= nil then
        hydrated.weaponPacked = sub.weaponPacked
    end

    if type(sub.desc_i18n) == "table" then
        hydrated.desc_i18n = sub.desc_i18n
    else
        hydrated.desc_i18n = {
            pt = hydrated.descPt,
            en = hydrated.descEn,
            es = hydrated.descEs,
            pl = hydrated.descPl
        }
    end

    if type(sub.subs) == "table" then
        hydrated.subs = {}
        for i, nested in ipairs(sub.subs) do
            hydrated.subs[i] = inheritSubFromMeta(char, nested)
        end
    end

    return hydrated
end

-- ===== Herança automática de descrições =====

local function findDescInSkills(data, skillName)
    if not data or not data.skills then return nil end
    local sk = data.skills[skillName]
    if sk and type(sk.desc) == "table" then
        return sk.desc
    end
    return nil
end

local function findDescInAnySubskills(data, skillName)
    if not data or not data.skills then return nil end
    for _, sk in pairs(data.skills) do
        if type(sk) == "table" and type(sk.subskills) == "table" then
            local sub = sk.subskills[skillName]
            if sub and type(sub.desc) == "table" then
                return sub.desc
            end
        end
    end
    return nil
end

local function resolveDescWithInheritance(data, skillName, directDesc)
    if directDesc and type(directDesc) == "table" and next(directDesc) then
        return directDesc
    end
    local fromSkills = findDescInSkills(data, skillName)
    if fromSkills then
        return fromSkills
    end
    local fromSubskills = findDescInAnySubskills(data, skillName)
    if fromSubskills then
        return fromSubskills
    end
    return nil
end

-- ===== Weapon =====
-- Retorna formato: icon~pve~pvp~cd~video~energy (separado por ~)
-- Isso evita problemas com JSON dentro de templates aninhados

function p.weapon(frame)
    local a = safeArgs(frame)
    local icon = trim(a.icon or "") ~= "" and a.icon or "Nada.png"
    local pve = trim(a.powerpve or "")
    local pvp = trim(a.powerpvp or "")
    local cd = trim(a.cooldown or "")
    local video = trim(a.video or "")
    local energy = trim(a.energy or "")
    -- Formato: icon~pve~pvp~cd~video~energy
    return icon .. "~" .. pve .. "~" .. pvp .. "~" .. cd .. "~" .. video .. "~" .. energy
end

-- Helper para parsear formato de weapon (icon~pve~pvp~cd~video~energy)
local function parseWeaponString(str)
    if not str or trim(str) == "" then return nil end
    str = trim(str)

    -- Se começa com {, é JSON (formato antigo)
    if str:sub(1, 1) == "{" then
        local ok, data = pcall(mw.text.jsonDecode, str)
        if ok and type(data) == "table" then
            local result = {
                icon = data.icon or "Nada.png"
            }
            -- Só inclui campos que existem (não inclui nil)
            if data.powerpve ~= nil then result.powerpve = data.powerpve end
            if data.powerpvp ~= nil then result.powerpvp = data.powerpvp end
            if data.cooldown ~= nil then result.cooldown = data.cooldown end
            if data.energy ~= nil then result.energy = data.energy end
            if data.video and trim(data.video) ~= "" then result.video = data.video end
            return result
        end
        return nil
    end

    -- Formato novo: icon~pve~pvp~cd~video~energy
    -- IMPORTANTE: precisa preservar campos vazios para manter ordem fixa
    local parts = {}
    -- Split manual que preserva campos vazios
    local start = 1
    while true do
        local pos = str:find("~", start)
        if not pos then
            -- Último campo
            table.insert(parts, trim(str:sub(start)))
            break
        end
        table.insert(parts, trim(str:sub(start, pos - 1)))
        start = pos + 1
    end
    -- Garante que temos pelo menos 6 campos (preenche com "" se necessário)
    while #parts < 6 do
        table.insert(parts, "")
    end
    -- Também aceita formato com vírgula: pve,pvp,cd,video
    if #parts < 2 then
        parts = {}
        for part in str:gmatch("[^,]+") do
            table.insert(parts, trim(part))
        end
        if #parts >= 1 then
            local result = {
                icon = "Nada.png"
            }
            -- Só inclui campos que existem (não inclui nil)
            if nz(parts[1]) then result.powerpve = parts[1] end
            if nz(parts[2]) then result.powerpvp = parts[2] end
            if nz(parts[3]) then result.cooldown = parts[3] end
            if parts[4] and trim(parts[4]) ~= "" then result.video = parts[4] end
            if nz(parts[5]) then result.energy = parts[5] end
            return result
        end
        return nil
    end

    -- Garante que temos pelo menos as partes básicas (icon sempre existe)
    local result = {
        icon = nz(parts[1]) or "Nada.png"
    }
    -- Só inclui campos que existem (não inclui nil)
    if nz(parts[2]) then result.powerpve = parts[2] end
    if nz(parts[3]) then result.powerpvp = parts[3] end
    if nz(parts[4]) then result.cooldown = parts[4] end
    if parts[5] and trim(parts[5]) ~= "" then result.video = parts[5] end
    if nz(parts[6]) then result.energy = parts[6] end
    return result
end

local function assignWeaponFromArg(obj, weaponStr)
    if not obj then return end
    local packed = trim(weaponStr or "")
    if packed ~= "" then
        -- Remove quebras de linha e espaços extras que podem vir da expansão do MediaWiki
        packed = packed:gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "")
        obj.weaponPacked = packed
        local parsed = parseWeaponString(packed)
        if parsed then
            obj.weapon = parsed
        end
    end
end

-- Normaliza uma tabela weapon vinda do módulo do personagem
-- Aceita tanto weapon.desc quanto weapon.desc_i18n e sempre retorna weapon.desc_i18n
local function normalizeWeaponTable(raw)
    if type(raw) ~= "table" then
        return nil
    end

    local w = {}
    -- Só inclui campos que existem (não inclui nil)
    if raw.icon then w.icon = raw.icon else w.icon = "Nada.png" end
    if raw.powerpve ~= nil then w.powerpve = raw.powerpve end
    if raw.powerpvp ~= nil then w.powerpvp = raw.powerpvp end
    if raw.cooldown ~= nil then w.cooldown = raw.cooldown end
    if raw.energy ~= nil then w.energy = raw.energy end
    -- Só inclui video se não estiver vazio
    if raw.video and trim(raw.video) ~= "" then
        w.video = raw.video
    end

    -- Aceita tanto raw.desc_i18n quanto raw.desc
    local src = nil
    if type(raw.desc_i18n) == "table" then
        src = raw.desc_i18n
    elseif type(raw.desc) == "table" then
        src = raw.desc
    end

    if src then
        local desc_i18n = {}
        for _, code in ipairs({ "pt", "en", "es", "pl" }) do
            local d = src[code]
            if d and trim(d) ~= "" then
                -- Usa a função colorize já existente no módulo
                desc_i18n[code] = colorize(d)
            end
        end
        if next(desc_i18n) ~= nil then
            w.desc_i18n = desc_i18n
        end
    end

    return w
end

local function ensureWeaponHydrated(sub)
    if type(sub) ~= "table" then return end
    -- Se weapon já é uma tabela válida, normaliza para garantir desc_i18n
    if sub.weapon and type(sub.weapon) == "table" and next(sub.weapon) ~= nil then
        local normalized = normalizeWeaponTable(sub.weapon)
        if normalized then
            sub.weapon = normalized
        end
        return -- Já está hidratado e normalizado
    end
    -- Se weapon é string, tenta parsear
    if sub.weapon and type(sub.weapon) ~= "table" then
        local parsed = parseWeaponString(sub.weapon)
        if parsed and next(parsed) ~= nil then
            sub.weapon = parsed
        else
            -- Se parse falhou mas tem weaponPacked, tenta depois
            if not sub.weaponPacked then
                sub.weaponPacked = sub.weapon
            end
            sub.weapon = nil
        end
    end
    -- Se não tem weapon mas tem weaponPacked, tenta parsear
    if not sub.weapon and sub.weaponPacked and trim(sub.weaponPacked) ~= "" then
        local parsed = parseWeaponString(sub.weaponPacked)
        if parsed and next(parsed) ~= nil then
            sub.weapon = parsed
        end
    end
end

local function normalizeSubTree(sub)
    if type(sub) ~= "table" then return end
    -- Preserva weapon antes de hidratar
    local hadWeapon = sub.weapon ~= nil
    local weaponBefore = sub.weapon
    ensureWeaponHydrated(sub)
    -- Se tinha weapon mas perdeu após hidratação, tenta recuperar do weaponPacked
    if hadWeapon and not sub.weapon and sub.weaponPacked then
        local parsed = parseWeaponString(sub.weaponPacked)
        if parsed and next(parsed) ~= nil then
            sub.weapon = parsed
        end
    end
    -- Se tinha weapon como objeto válido antes, preserva mesmo se ensureWeaponHydrated removeu
    if weaponBefore and type(weaponBefore) == "table" and next(weaponBefore) ~= nil and not sub.weapon then
        sub.weapon = weaponBefore
    end
    if type(sub.subs) == "table" then
        for _, nested in ipairs(sub.subs) do
            normalizeSubTree(nested)
        end
    end
    -- Só remove weaponPacked se weapon está válido
    if sub.weapon and type(sub.weapon) == "table" and next(sub.weapon) ~= nil then
        sub.weaponPacked = nil
    end
end

-- ===== Skill (com M) =====

function p.skill(frame)
    local a = safeArgs(frame)
    local lang = trim((a.lang or (frame:getParent() and frame:getParent().args.lang) or "pt"):lower())
    local char = resolveCharFromFrames(frame, a)
    local data = requireCharacterModule(char) or {}

    local name, desc_i18n = nil, {}
    if a.M and trim(a.M) ~= "" then
        local m = tonumber(a.M) or 0
        local order = data.order or {}
        local key = order[m] or ""
        local sk = (data.skills or {})[key] or {}
        name = trim(sk.name or key or "")
        if type(sk.desc) == "table" then
            local langs = { "pt", "en", "es", "pl" }
            for _, code in ipairs(langs) do
                local d = sk.desc[code]
                if d and trim(d) ~= "" then
                    desc_i18n[code] = colorize(d)
                end
            end
        elseif sk.desc and trim(sk.desc) ~= "" then
            desc_i18n["pt"] = colorize(sk.desc)
        end
    end

    local chosen = desc_i18n[lang] or desc_i18n["pt"] or ""
    local flagsArr = parseFlags(a.flags)

    local obj = {
        icon = (trim(a.icon or "") ~= "" and a.icon or "Nada.png"),
        level = (trim(a.level or "") ~= "" and a.level or "NIVEL"),
        energy = nz(a.energy),
        powerpve = nz(a.powerpve),
        powerpvp = nz(a.powerpvp),
        cooldown = nz(a.cooldown),
        video = a.video or "",
        name = name
    }
    if #flagsArr > 0 then
        obj.flags = flagsArr
    end
    if chosen ~= "" then
        obj.desc = chosen
    end
    if next(desc_i18n) ~= nil then
        obj.desc_i18n = desc_i18n
        obj.descPt = desc_i18n.pt
        obj.descEn = desc_i18n.en
        obj.descEs = desc_i18n.es
        obj.descPl = desc_i18n.pl
    end

    -- Registra metadados desta skill para herança futura
    if a.M and trim(a.M) ~= "" then
        local metaData = {
            name = obj.name or name or "",
            icon = obj.icon,
            level = obj.level,
            energy = obj.energy,
            powerpve = obj.powerpve,
            powerpvp = obj.powerpvp,
            cooldown = obj.cooldown,
            video = obj.video,
            desc = obj.desc,
            descPt = obj.descPt,
            descEn = obj.descEn,
            descEs = obj.descEs,
            descPl = obj.descPl
        }
        storeSkillMeta(char, tonumber(a.M) or a.M, metaData.name, metaData)
    end

    -- Subskills vindas via |subs=
    do
        local subsPacked = a.subs or ""
        local subsArr = {}

        -- Primeiro: extrai índices numéricos do início (antes dos JSONs)
        -- Formato: "1,2,3,4,5 {json1}{json2}" ou só "1,2,3,4,5"
        local indexPart = subsPacked:match("^([%s%d,]+)")
        local addedIndices = {} -- Track which indices were added
        if indexPart then
            for idx in indexPart:gmatch("(%d+)") do
                local i = tonumber(idx)
                if i and data and data.order and data.order[i] then
                    local skillName = data.order[i]
                    local skillData = (data.skills or {})[skillName] or {}
                    local subObj = {
                        refS = tostring(i),
                        name = skillName,
                        n = skillName,
                        icon = "", -- JS vai herdar da skill principal
                        level = "",
                        video = "",
                        energy = nil,
                        powerpve = nil,
                        powerpvp = nil,
                        cooldown = nil
                    }
                    -- Busca descrição do módulo
                    if type(skillData.desc) == "table" then
                        local desc_i18n = {}
                        local langs = { "pt", "en", "es", "pl" }
                        for _, code in ipairs(langs) do
                            local d = skillData.desc[code]
                            if d and trim(d) ~= "" then
                                desc_i18n[code] = colorize(d)
                            end
                        end
                        if next(desc_i18n) then
                            subObj.desc_i18n = desc_i18n
                            subObj.descPt = desc_i18n.pt
                            subObj.descEn = desc_i18n.en
                            subObj.descEs = desc_i18n.es
                            subObj.descPl = desc_i18n.pl
                        end
                    end
                    table.insert(subsArr, subObj)
                    addedIndices[tostring(i)] = #subsArr -- Store position
                end
            end
        end

        -- Depois: processa JSONs (das {{Subskill}} expandidas)
        local chunks = collectJsonObjects(subsPacked)

        -- Se um JSON tem refM que já foi adicionado via índice, substitui/merge
        for _, chunk in ipairs(chunks) do
            local ok, sk = pcall(mw.text.jsonDecode, chunk)
            if ok and type(sk) == "table" then
                ensureWeaponHydrated(sk)
                local ref = sk.refM or sk.M or sk.m or sk.refS or sk.S or sk.s
                if ref and addedIndices[tostring(ref)] then
                    -- Merge: atualiza o objeto existente com os atributos do JSON
                    local pos = addedIndices[tostring(ref)]
                    local existing = subsArr[pos]
                    -- Sobrescreve apenas atributos não vazios do JSON
                    if sk.video and trim(sk.video) ~= "" then existing.video = sk.video end
                    if sk.icon and trim(sk.icon) ~= "" then existing.icon = sk.icon end
                    if sk.level and trim(tostring(sk.level)) ~= "" then existing.level = sk.level end
                    if sk.energy ~= nil then existing.energy = sk.energy end
                    if sk.powerpve ~= nil then existing.powerpve = sk.powerpve end
                    if sk.powerpvp ~= nil then existing.powerpvp = sk.powerpvp end
                    if sk.cooldown ~= nil then existing.cooldown = sk.cooldown end
                    if sk.weapon ~= nil then existing.weapon = sk.weapon end
                    if sk.weaponPacked ~= nil then existing.weaponPacked = sk.weaponPacked end

                    -- BUSCA WEAPON DO MÓDULO LUA para fazer merge
                    -- Se weapon já existe mas não tem desc_i18n, busca do módulo para completar
                    local subName = existing.name or existing.n or ""
                    if subName ~= "" then
                        for _, skillKey in ipairs(data.order or {}) do
                            local skillData = (data.skills or {})[skillKey]
                            if type(skillData) == "table" and type(skillData.subskills) == "table" then
                                local moduleSub = skillData.subskills[subName]
                                if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
                                    local moduleWeapon = normalizeWeaponTable(moduleSub.weapon)
                                    if moduleWeapon then
                                        if existing.weapon and type(existing.weapon) == "table" then
                                            -- Merge: preserva atributos do template, adiciona desc_i18n do módulo
                                            if moduleWeapon.desc_i18n and (not existing.weapon.desc_i18n) then
                                                existing.weapon.desc_i18n = moduleWeapon.desc_i18n
                                            end
                                            -- Se não tem icon no template, usa do módulo
                                            if (not existing.weapon.icon or existing.weapon.icon == "Nada.png") and moduleWeapon.icon then
                                                existing.weapon.icon = moduleWeapon.icon
                                            end
                                            -- Se não tem atributos no template, usa do módulo
                                            if existing.weapon.powerpve == nil and moduleWeapon.powerpve ~= nil then
                                                existing.weapon.powerpve = moduleWeapon.powerpve
                                            end
                                            if existing.weapon.powerpvp == nil and moduleWeapon.powerpvp ~= nil then
                                                existing.weapon.powerpvp = moduleWeapon.powerpvp
                                            end
                                            if existing.weapon.cooldown == nil and moduleWeapon.cooldown ~= nil then
                                                existing.weapon.cooldown = moduleWeapon.cooldown
                                            end
                                            if existing.weapon.energy == nil and moduleWeapon.energy ~= nil then
                                                existing.weapon.energy = moduleWeapon.energy
                                            end
                                            if (not existing.weapon.video or existing.weapon.video == "") and moduleWeapon.video then
                                                existing.weapon.video = moduleWeapon.video
                                            end
                                        else
                                            -- Se não tem weapon ainda, usa o do módulo
                                            existing.weapon = moduleWeapon
                                        end
                                        break
                                    end
                                end
                            end
                        end
                    end

                    -- Garante que weapon está hidratado após merge
                    ensureWeaponHydrated(existing)
                    -- Marca como já processado para não adicionar de novo
                    sk._merged = true
                end
            end
        end
        for _, chunk in ipairs(chunks) do
            local ok, sk = pcall(mw.text.jsonDecode, chunk)
            if ok and type(sk) == "table" then
                ensureWeaponHydrated(sk)
                -- Pula se já foi mergeado com um índice
                local ref = sk.refM or sk.M or sk.m or sk.refS or sk.S or sk.s
                local skipChunk = false
                if ref and addedIndices[tostring(ref)] then
                    -- Já foi processado no merge acima, pula
                    skipChunk = true
                end

                if not skipChunk then
                    -- Se tem ref (M ou S), aplica herança do cache OU busca diretamente
                    if ref and trim(tostring(ref)) ~= "" then
                        local mainMeta = fetchSkillMeta(char, ref)

                        -- Se não tem no cache, ref corresponde a M=N, então busca pelo índice
                        if not mainMeta then
                            local idx = tonumber(ref)
                            if idx and data and data.order and data.order[idx] then
                                local skillName = data.order[idx]
                                local skillData = (data.skills or {})[skillName] or {}
                                -- Cria um meta básico com dados do módulo
                                mainMeta = {
                                    name = skillName,
                                    icon = "", -- será preenchido pelo JS
                                    level = "",
                                    video = "",
                                    energy = nil,
                                    powerpve = nil,
                                    powerpvp = nil,
                                    cooldown = nil
                                }
                                -- Busca descrição
                                if type(skillData.desc) == "table" then
                                    local langs = { "pt", "en", "es", "pl" }
                                    for _, code in ipairs(langs) do
                                        local d = skillData.desc[code]
                                        if d and trim(d) ~= "" then
                                            mainMeta["desc" .. code:sub(1, 1):upper() .. code:sub(2)] = colorize(d)
                                        end
                                    end
                                end
                            end
                        end

                        if mainMeta then
                            -- Preenche nome se não tiver
                            if (not sk.name or trim(sk.name) == "") and (not sk.n or trim(sk.n) == "") then
                                sk.name = mainMeta.name
                                sk.n = mainMeta.name
                            end
                            -- Herda atributos vazios
                            if not sk.icon or trim(sk.icon) == "" then sk.icon = mainMeta.icon end
                            if not sk.level or trim(tostring(sk.level)) == "" then sk.level = mainMeta.level end
                            if not sk.video or trim(sk.video) == "" then sk.video = mainMeta.video end
                            if sk.energy == nil then sk.energy = mainMeta.energy end
                            if sk.powerpve == nil then sk.powerpve = mainMeta.powerpve end
                            if sk.powerpvp == nil then sk.powerpvp = mainMeta.powerpvp end
                            if sk.cooldown == nil then sk.cooldown = mainMeta.cooldown end
                            -- Herda descrição
                            if not sk.desc_i18n and not sk.descPt then
                                -- Busca descrição do módulo pelo nome
                                local foundDesc = resolveDescWithInheritance(data, mainMeta.name, nil)
                                if foundDesc then
                                    local langs = { "pt", "en", "es", "pl" }
                                    for _, code in ipairs(langs) do
                                        local d = foundDesc[code]
                                        if d and trim(d) ~= "" then
                                            sk["desc" .. code:sub(1, 1):upper() .. code:sub(2)] = colorize(d)
                                        end
                                    end
                                    sk.desc_i18n = {
                                        pt = sk.descPt,
                                        en = sk.descEn,
                                        es = sk.descEs,
                                        pl = sk.descPl
                                    }
                                elseif mainMeta.descPt or mainMeta.descEn then
                                    sk.descPt = mainMeta.descPt
                                    sk.descEn = mainMeta.descEn
                                    sk.descEs = mainMeta.descEs
                                    sk.descPl = mainMeta.descPl
                                    sk.desc_i18n = {
                                        pt = mainMeta.descPt,
                                        en = mainMeta.descEn,
                                        es = mainMeta.descEs,
                                        pl = mainMeta.descPl
                                    }
                                end
                            end
                        end

                        -- Mesmo sem mainMeta, mantém refM para o JS processar
                        if not mainMeta then
                            sk.refM = ref
                        end
                    end
                    -- Fallback: Se tem refM explícito no JSON, aplica herança do cache
                    local refMExplicit = sk.refM or sk.M or sk.m
                    if refMExplicit and trim(tostring(refMExplicit)) ~= "" and not ref then
                        local mainMeta = fetchSkillMeta(char, refMExplicit)
                        if mainMeta then
                            -- Preenche nome se não tiver
                            if (not sk.name or trim(sk.name) == "") and (not sk.n or trim(sk.n) == "") then
                                sk.name = mainMeta.name
                                sk.n = mainMeta.name
                            end
                            -- Herda atributos vazios
                            if not sk.icon or trim(sk.icon) == "" then sk.icon = mainMeta.icon end
                            if not sk.level or trim(tostring(sk.level)) == "" then sk.level = mainMeta.level end
                            if not sk.video or trim(sk.video) == "" then sk.video = mainMeta.video end
                            if sk.energy == nil then sk.energy = mainMeta.energy end
                            if sk.powerpve == nil then sk.powerpve = mainMeta.powerpve end
                            if sk.powerpvp == nil then sk.powerpvp = mainMeta.powerpvp end
                            if sk.cooldown == nil then sk.cooldown = mainMeta.cooldown end
                            -- Herda descrição
                            if not sk.desc_i18n and not sk.descPt then
                                sk.descPt = mainMeta.descPt
                                sk.descEn = mainMeta.descEn
                                sk.descEs = mainMeta.descEs
                                sk.descPl = mainMeta.descPl
                                sk.desc_i18n = {
                                    pt = mainMeta.descPt,
                                    en = mainMeta.descEn,
                                    es = mainMeta.descEs,
                                    pl = mainMeta.descPl
                                }
                            end
                        end
                    end

                    -- BUSCA WEAPON DO MÓDULO LUA para fazer merge
                    -- Se weapon já existe mas não tem desc_i18n, busca do módulo para completar
                    local subName = sk.name or sk.n or ""
                    if subName ~= "" then
                        for _, skillKey in ipairs(data.order or {}) do
                            local skillData = (data.skills or {})[skillKey]
                            if type(skillData) == "table" and type(skillData.subskills) == "table" then
                                local moduleSub = skillData.subskills[subName]
                                if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
                                    local moduleWeapon = normalizeWeaponTable(moduleSub.weapon)
                                    if moduleWeapon then
                                        if sk.weapon and type(sk.weapon) == "table" then
                                            -- Merge: preserva atributos do template, adiciona desc_i18n do módulo
                                            if moduleWeapon.desc_i18n and (not sk.weapon.desc_i18n) then
                                                sk.weapon.desc_i18n = moduleWeapon.desc_i18n
                                            end
                                            -- Se não tem icon no template, usa do módulo
                                            if (not sk.weapon.icon or sk.weapon.icon == "Nada.png") and moduleWeapon.icon then
                                                sk.weapon.icon = moduleWeapon.icon
                                            end
                                            -- Se não tem atributos no template, usa do módulo
                                            if sk.weapon.powerpve == nil and moduleWeapon.powerpve ~= nil then
                                                sk.weapon.powerpve = moduleWeapon.powerpve
                                            end
                                            if sk.weapon.powerpvp == nil and moduleWeapon.powerpvp ~= nil then
                                                sk.weapon.powerpvp = moduleWeapon.powerpvp
                                            end
                                            if sk.weapon.cooldown == nil and moduleWeapon.cooldown ~= nil then
                                                sk.weapon.cooldown = moduleWeapon.cooldown
                                            end
                                            if sk.weapon.energy == nil and moduleWeapon.energy ~= nil then
                                                sk.weapon.energy = moduleWeapon.energy
                                            end
                                            if (not sk.weapon.video or sk.weapon.video == "") and moduleWeapon.video then
                                                sk.weapon.video = moduleWeapon.video
                                            end
                                        else
                                            -- Se não tem weapon ainda, usa o do módulo
                                            sk.weapon = moduleWeapon
                                        end
                                        break
                                    end
                                end
                            end
                        end
                    end

                    -- Garante que weapon está normalizado antes de adicionar
                    ensureWeaponHydrated(sk)
                    table.insert(subsArr, sk)
                end
            end -- end if not skipChunk
        end
        -- Fallback: subskills do módulo .lua
        if #subsArr == 0 and a.M and tonumber(a.M) then
            local m = tonumber(a.M)
            local order = (data and data.order) or {}
            local key = order[m] or ""
            local sk = (data and data.skills and data.skills[key]) or {}
            local suborder = type(sk) == "table" and sk.suborder or nil
            local subskills = type(sk) == "table" and sk.subskills or nil
            if type(suborder) == "table" and type(subskills) == "table" then
                for _, n in ipairs(suborder) do
                    local sub = subskills[n]
                    if type(sub) == "table" then
                        local resolvedDesc = resolveDescWithInheritance(data, n, sub.desc)

                        -- Se tem inherit, busca atributos da skill principal referenciada
                        local inheritIdx = sub.inherit
                        local inheritedIcon, inheritedLevel, inheritedVideo = "", "", ""
                        local inheritedEnergy, inheritedPvE, inheritedPvP, inheritedCD = nil, nil, nil, nil

                        if inheritIdx and tonumber(inheritIdx) then
                            local mainSkillMeta = fetchSkillMeta(char, inheritIdx)
                            if mainSkillMeta then
                                inheritedIcon = mainSkillMeta.icon or ""
                                inheritedLevel = mainSkillMeta.level or ""
                                inheritedVideo = mainSkillMeta.video or ""
                                inheritedEnergy = mainSkillMeta.energy
                                inheritedPvE = mainSkillMeta.powerpve
                                inheritedPvP = mainSkillMeta.powerpvp
                                inheritedCD = mainSkillMeta.cooldown
                                -- Se não tem descrição própria, herda da skill principal
                                if not resolvedDesc then
                                    resolvedDesc = {
                                        pt = mainSkillMeta.descPt,
                                        en = mainSkillMeta.descEn,
                                        es = mainSkillMeta.descEs,
                                        pl = mainSkillMeta.descPl
                                    }
                                end
                            end
                        end

                        -- Coloriza as descrições
                        local desc_i18n = {}
                        if resolvedDesc then
                            local langs = { "pt", "en", "es", "pl" }
                            for _, code in ipairs(langs) do
                                local d = resolvedDesc[code]
                                if d and trim(d) ~= "" then
                                    desc_i18n[code] = colorize(d)
                                end
                            end
                        end

                        local subObj = {
                            n = n,
                            name = n,
                            icon = (sub.icon and sub.icon ~= "") and sub.icon or inheritedIcon,
                            level = (sub.level and sub.level ~= "") and tostring(sub.level) or inheritedLevel,
                            video = (sub.video and sub.video ~= "") and sub.video or inheritedVideo,
                            energy = sub.energy or inheritedEnergy,
                            powerpve = sub.powerpve or inheritedPvE,
                            powerpvp = sub.powerpvp or inheritedPvP,
                            cooldown = sub.cooldown or inheritedCD,
                            desc_i18n = next(desc_i18n) and desc_i18n or nil,
                            descPt = desc_i18n.pt or nil,
                            descEn = desc_i18n.en or nil,
                            descEs = desc_i18n.es or nil,
                            descPl = desc_i18n.pl or nil
                        }
                        -- INCLUI WEAPON do módulo Lua se existir
                        if type(sub.weapon) == "table" then
                            local weaponObj = normalizeWeaponTable(sub.weapon)
                            if weaponObj then
                                subObj.weapon = weaponObj
                            end
                        end
                        if type(sub.suborder) == "table" and type(sub.subskills) == "table" then
                            local nested = {}
                            for _, nn in ipairs(sub.suborder) do
                                local s2 = sub.subskills[nn]
                                if type(s2) == "table" then
                                    local nestedDesc = resolveDescWithInheritance(data, nn, s2.desc)
                                    local nestedObj = {
                                        n = nn,
                                        name = nn,
                                        icon = s2.icon or "",
                                        level = s2.level or "",
                                        video = s2.video or "",
                                        energy = s2.energy,
                                        powerpve = s2.powerpve,
                                        powerpvp = s2.powerpvp,
                                        cooldown = s2.cooldown,
                                        desc_i18n = nestedDesc,
                                        descPt = nestedDesc and nestedDesc.pt or nil,
                                        descEn = nestedDesc and nestedDesc.en or nil,
                                        descEs = nestedDesc and nestedDesc.es or nil,
                                        descPl = nestedDesc and nestedDesc.pl or nil
                                    }
                                    -- INCLUI WEAPON do nested se existir
                                    if type(s2.weapon) == "table" then
                                        local weaponObj = normalizeWeaponTable(s2.weapon)
                                        if weaponObj then
                                            nestedObj.weapon = weaponObj
                                        end
                                    end
                                    table.insert(nested, nestedObj)
                                end
                            end
                            if #nested > 0 then subObj.subs = nested end
                        end
                        table.insert(subsArr, subObj)
                    end
                end
            end
        end
        if #subsArr > 0 then
            for i, sub in ipairs(subsArr) do
                subsArr[i] = inheritSubFromMeta(char, sub)
                -- BUSCA WEAPON DO MÓDULO LUA se não tem weapon ainda (após herança)
                local hasWeapon = subsArr[i].weapon and (
                    (type(subsArr[i].weapon) == "table" and next(subsArr[i].weapon) ~= nil) or
                    (type(subsArr[i].weapon) == "string" and trim(subsArr[i].weapon) ~= "")
                )
                if not hasWeapon then
                    local subName = subsArr[i].name or subsArr[i].n or ""
                    if subName ~= "" then
                        for _, skillKey in ipairs(data.order or {}) do
                            local skillData = (data.skills or {})[skillKey]
                            if type(skillData) == "table" and type(skillData.subskills) == "table" then
                                local moduleSub = skillData.subskills[subName]
                                if moduleSub then
                                    -- DEBUG: Log temporário
                                    if subName == "Karmic Jishin" then
                                        -- Verifica se tem weapon
                                    end
                                end
                                if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
                                    local weaponObj = {
                                        icon = moduleSub.weapon.icon or "Nada.png",
                                        energy = moduleSub.weapon.energy,
                                        powerpve = moduleSub.weapon.powerpve,
                                        powerpvp = moduleSub.weapon.powerpvp,
                                        cooldown = moduleSub.weapon.cooldown,
                                        video = moduleSub.weapon.video or ""
                                    }
                                    if type(moduleSub.weapon.desc) == "table" then
                                        local weaponDesc = {}
                                        for _, code in ipairs({ "pt", "en", "es", "pl" }) do
                                            local d = moduleSub.weapon.desc[code]
                                            if d and trim(d) ~= "" then
                                                weaponDesc[code] = colorize(d)
                                            end
                                        end
                                        if next(weaponDesc) then
                                            weaponObj.desc_i18n = weaponDesc
                                        end
                                    end
                                    subsArr[i].weapon = weaponObj
                                    -- DEBUG: Log para confirmar que encontrou
                                    if subName == "Karmic Jishin" then
                                        -- Log temporário
                                    end
                                    break
                                end
                            end
                        end
                    end
                end
                normalizeSubTree(subsArr[i])
                -- VERIFICAÇÃO FINAL: Garante que weapon está presente e completo antes de serializar
                -- Busca do módulo para fazer merge (mesmo se já tem weapon do template)
                local subName = subsArr[i].name or subsArr[i].n or ""
                if subName ~= "" then
                    for _, skillKey in ipairs(data.order or {}) do
                        local skillData = (data.skills or {})[skillKey]
                        if type(skillData) == "table" and type(skillData.subskills) == "table" then
                            local moduleSub = skillData.subskills[subName]
                            if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
                                local moduleWeapon = normalizeWeaponTable(moduleSub.weapon)
                                if moduleWeapon then
                                    if subsArr[i].weapon and type(subsArr[i].weapon) == "table" then
                                        -- Merge: preserva atributos do template, adiciona desc_i18n do módulo
                                        if moduleWeapon.desc_i18n and (not subsArr[i].weapon.desc_i18n) then
                                            subsArr[i].weapon.desc_i18n = moduleWeapon.desc_i18n
                                        end
                                        -- Se não tem icon no template, usa do módulo
                                        if (not subsArr[i].weapon.icon or subsArr[i].weapon.icon == "Nada.png") and moduleWeapon.icon then
                                            subsArr[i].weapon.icon = moduleWeapon.icon
                                        end
                                        -- Se não tem atributos no template, usa do módulo
                                        if subsArr[i].weapon.powerpve == nil and moduleWeapon.powerpve ~= nil then
                                            subsArr[i].weapon.powerpve = moduleWeapon.powerpve
                                        end
                                        if subsArr[i].weapon.powerpvp == nil and moduleWeapon.powerpvp ~= nil then
                                            subsArr[i].weapon.powerpvp = moduleWeapon.powerpvp
                                        end
                                        if subsArr[i].weapon.cooldown == nil and moduleWeapon.cooldown ~= nil then
                                            subsArr[i].weapon.cooldown = moduleWeapon.cooldown
                                        end
                                        if subsArr[i].weapon.energy == nil and moduleWeapon.energy ~= nil then
                                            subsArr[i].weapon.energy = moduleWeapon.energy
                                        end
                                        if (not subsArr[i].weapon.video or subsArr[i].weapon.video == "") and moduleWeapon.video then
                                            subsArr[i].weapon.video = moduleWeapon.video
                                        end
                                    else
                                        -- Se não tem weapon ainda, usa o do módulo
                                        subsArr[i].weapon = moduleWeapon
                                    end
                                    break
                                end
                            end
                        end
                    end
                end
            end
            obj.subs = subsArr
            -- SISTEMA UNIFICADO DE WEAPON PARA SUBSKILLS: Garante weapon normalizado e completo antes de serializar
            if type(obj.subs) == "table" then
                for i, sub in ipairs(obj.subs) do
                    local subName = sub.name or sub.n or ""

                    -- PASSO 1: Normaliza weapon existente
                    ensureWeaponHydrated(sub)
                    normalizeSubTree(sub)

                    -- PASSO 2: Se weapon ainda é string, tenta parsear
                    if sub.weapon and type(sub.weapon) ~= "table" then
                        local parsed = parseWeaponString(sub.weapon)
                        if parsed and next(parsed) ~= nil then
                            sub.weapon = parsed
                        else
                            sub.weapon = nil
                        end
                    end

                    -- PASSO 3: Busca weapon do módulo (sempre, para garantir merge completo)
                    if subName ~= "" then
                        for _, skillKey in ipairs(data.order or {}) do
                            local skillData = (data.skills or {})[skillKey]
                            if type(skillData) == "table" and type(skillData.subskills) == "table" then
                                -- Tenta match exato primeiro
                                local moduleSub = skillData.subskills[subName]
                                -- Se não encontrou, tenta case-insensitive
                                if not moduleSub then
                                    for k, v in pairs(skillData.subskills) do
                                        if type(k) == "string" and k:lower() == subName:lower() then
                                            moduleSub = v
                                            break
                                        end
                                    end
                                end

                                -- Se encontrou subskill no módulo com weapon, faz merge completo
                                if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
                                    local moduleWeapon = normalizeWeaponTable(moduleSub.weapon)
                                    if moduleWeapon then
                                        if obj.subs[i].weapon and type(obj.subs[i].weapon) == "table" then
                                            -- MERGE: Combina weapon do template com weapon do módulo
                                            -- Descrição do módulo sempre tem prioridade (tem desc_i18n completo)
                                            if moduleWeapon.desc_i18n then
                                                obj.subs[i].weapon.desc_i18n = moduleWeapon.desc_i18n
                                            end
                                            -- Atributos: usa do módulo se não tem no template
                                            if not obj.subs[i].weapon.icon or obj.subs[i].weapon.icon == "Nada.png" then
                                                obj.subs[i].weapon.icon = moduleWeapon.icon
                                            end
                                            if obj.subs[i].weapon.powerpve == nil then
                                                obj.subs[i].weapon.powerpve = moduleWeapon.powerpve
                                            end
                                            if obj.subs[i].weapon.powerpvp == nil then
                                                obj.subs[i].weapon.powerpvp = moduleWeapon.powerpvp
                                            end
                                            if obj.subs[i].weapon.cooldown == nil then
                                                obj.subs[i].weapon.cooldown = moduleWeapon.cooldown
                                            end
                                            if obj.subs[i].weapon.energy == nil then
                                                obj.subs[i].weapon.energy = moduleWeapon.energy
                                            end
                                            if not obj.subs[i].weapon.video or obj.subs[i].weapon.video == "" then
                                                obj.subs[i].weapon.video = moduleWeapon.video
                                            end
                                        else
                                            -- Se não tem weapon ainda, usa o do módulo completo
                                            obj.subs[i].weapon = moduleWeapon
                                        end
                                        break
                                    end
                                end
                            end
                        end
                    end

                    -- PASSO 4: Garante que weapon está normalizado e válido
                    if obj.subs[i].weapon then
                        ensureWeaponHydrated(obj.subs[i])
                        -- Remove weaponPacked se weapon está válido
                        if type(obj.subs[i].weapon) == "table" and next(obj.subs[i].weapon) ~= nil then
                            obj.subs[i].weaponPacked = nil
                            -- Garante que desc_i18n existe se tem descrição
                            if obj.subs[i].weapon.desc_i18n and type(obj.subs[i].weapon.desc_i18n) == "table" then
                                -- Já está normalizado
                            elseif obj.subs[i].weapon.desc and type(obj.subs[i].weapon.desc) == "table" then
                                -- Converte desc para desc_i18n
                                local desc_i18n = {}
                                for _, code in ipairs({ "pt", "en", "es", "pl" }) do
                                    local d = obj.subs[i].weapon.desc[code]
                                    if d and trim(d) ~= "" then
                                        desc_i18n[code] = colorize(d)
                                    end
                                end
                                if next(desc_i18n) ~= nil then
                                    obj.subs[i].weapon.desc_i18n = desc_i18n
                                end
                            end
                        else
                            obj.subs[i].weapon = nil
                        end
                    end
                end
            end
        end

        -- suborder do módulo
        if a.M and tonumber(a.M) then
            local m = tonumber(a.M)
            local order = (data and data.order) or {}
            local key = order[m] or ""
            local sk = (data and data.skills and data.skills[key]) or {}
            if type(sk) == "table" and type(sk.suborder) == "table" then
                obj.suborder = sk.suborder
            end
        end
    end

    -- Weapon
    do
        assignWeaponFromArg(obj, a.weapon)
        if obj.weapon then
            if a.M and tonumber(a.M) then
                local m = tonumber(a.M)
                local order = (data and data.order) or {}
                local key = order[m] or ""
                local sk = (data and data.skills and data.skills[key]) or {}
                if type(sk) == "table" and type(sk.weapon) == "table" then
                    local normalized = normalizeWeaponTable(sk.weapon)
                    if normalized then
                        -- Preserva campos já existentes em obj.weapon se houver
                        if obj.weapon then
                            -- Merge inteligente: só sobrescreve se o campo do módulo não estiver vazio
                            -- E só sobrescreve se o campo do template estiver vazio ou não existir
                            for k, v in pairs(normalized) do
                                -- Para video: SEMPRE preserva o do template se existir (não faz merge)
                                if k == "video" then
                                    -- Se o template tem vídeo, preserva (não sobrescreve com módulo)
                                    if obj.weapon.video and trim(obj.weapon.video) ~= "" then
                                        -- Mantém o vídeo do template, não faz nada
                                    elseif v and v ~= "" then
                                        -- Só usa do módulo se template não tem
                                        obj.weapon.video = v
                                    end
                                    -- Para icon: preserva o do template se não for Nada.png
                                elseif k == "icon" then
                                    if (not obj.weapon.icon or obj.weapon.icon == "" or obj.weapon.icon == "Nada.png") and v and v ~= "" and v ~= "Nada.png" then
                                        obj.weapon.icon = v
                                    end
                                    -- Para outros campos: só faz merge se o campo NÃO EXISTE no template (nil)
                                    -- Se o campo existe no template (mesmo que vazio), preserva o do template
                                else
                                    -- IMPORTANTE: só faz merge se o campo é nil (não existe), não se está vazio
                                    if obj.weapon[k] == nil and v ~= nil and v ~= "" then
                                        obj.weapon[k] = v
                                    end
                                end
                            end
                            -- Sempre adiciona desc_i18n do módulo se não tiver no template
                            if normalized.desc_i18n and not obj.weapon.desc_i18n then
                                obj.weapon.desc_i18n = normalized.desc_i18n
                            end
                        else
                            obj.weapon = normalized
                        end
                        -- Adiciona campos descPt, descEn, etc. para compatibilidade
                        if obj.weapon.desc_i18n then
                            obj.weapon.descPt = obj.weapon.desc_i18n.pt
                            obj.weapon.descEn = obj.weapon.desc_i18n.en
                            obj.weapon.descEs = obj.weapon.desc_i18n.es
                            obj.weapon.descPl = obj.weapon.desc_i18n.pl
                        end
                    end
                end
            end
        end
    end

    return mw.text.jsonEncode(obj)
end

-- ===== Emote =====

function p.emote(frame)
    local a = safeArgs(frame)
    local obj = {
        name = "Emote",
        desc = "",
        icon = (trim(a.icon or "") ~= "" and a.icon or "Nada.png"),
        level = (trim(a.level or "") ~= "" and a.level or "NIVEL"),
        video = a.video or ""
    }
    return mw.text.jsonEncode(obj)
end

-- ===== Subskill =====
-- Gera JSON simples. Herança COMPLETA de atributos é feita no JavaScript.
-- Se |M= for passado sem |n=, retorna apenas refM para o JS resolver.

function p.subskill(frame)
    local a = safeArgs(frame)
    local lang = trim((a.lang or (frame:getParent() and frame:getParent().args.lang) or "pt"):lower())

    local n = trim(a.n or "")
    local refM = trim(a.m or a.M or "")
    local refS = trim(a.S or a.s or "")

    -- Prioriza |M= sobre |S= para herança
    local ref = (refM ~= "" and refM) or refS

    local char = resolveCharFromFrames(frame, a)
    local data = requireCharacterModule(char) or {}

    -- Se tem |M= ou |S=, busca do cache e herda atributos
    if ref ~= "" then
        local meta = fetchSkillMeta(char, ref)
        if meta then
            -- Herda atributos da skill referenciada, sobrescreve só o que foi passado
            local function pick(explicit, inherited)
                if explicit ~= nil and trim(tostring(explicit)) ~= "" then
                    return explicit
                end
                return inherited
            end

            -- Busca descrição: primeiro do módulo pelo nome herdado, depois usa a da skill
            local inheritedName = meta.name or ""
            local desc_i18n = {}
            local foundDesc = resolveDescWithInheritance(data, inheritedName, nil)
            if foundDesc then
                local langs = { "pt", "en", "es", "pl" }
                for _, code in ipairs(langs) do
                    local d = foundDesc[code]
                    if d and trim(d) ~= "" then
                        desc_i18n[code] = colorize(d)
                    end
                end
            end
            -- Se não achou no módulo, usa descrição da skill original
            if not next(desc_i18n) then
                desc_i18n = {
                    pt = meta.descPt,
                    en = meta.descEn,
                    es = meta.descEs,
                    pl = meta.descPl
                }
            end

            local obj = {
                name = pick(a.n, inheritedName),
                icon = pick(a.icon, meta.icon),
                level = pick(a.level, meta.level),
                video = pick(a.video, meta.video),
                energy = nz(a.energy) or meta.energy,
                powerpve = nz(a.powerpve) or meta.powerpve,
                powerpvp = nz(a.powerpvp) or meta.powerpvp,
                cooldown = nz(a.cooldown) or meta.cooldown,
                back = (trim(a.back or "") == "yes" or trim(a.back or "") == "true") and true or nil,
                refM = ref
            }

            local flagsArr = parseFlags(a.flags)
            if #flagsArr > 0 then
                obj.flags = flagsArr
            end
            if next(desc_i18n) then
                obj.desc_i18n = desc_i18n
                obj.descPt = desc_i18n.pt
                obj.descEn = desc_i18n.en
                obj.descEs = desc_i18n.es
                obj.descPl = desc_i18n.pl
            end

            -- Weapon para subskill
            assignWeaponFromArg(obj, a.weapon)

            return mw.text.jsonEncode(obj)
        else
            -- Cache vazio - busca nome e descrição diretamente do módulo
            -- ref corresponde ao índice M
            local idx = tonumber(ref)
            local skillName = ""
            local desc_i18n = {}

            if idx and data and data.order and data.order[idx] then
                skillName = data.order[idx]
                local skillData = (data.skills or {})[skillName] or {}
                if type(skillData.desc) == "table" then
                    local langs = { "pt", "en", "es", "pl" }
                    for _, code in ipairs(langs) do
                        local d = skillData.desc[code]
                        if d and trim(d) ~= "" then
                            desc_i18n[code] = colorize(d)
                        end
                    end
                end
            end

            local obj = {
                refM = ref,
                name = skillName,
                n = skillName,
                icon = trim(a.icon or ""),
                level = trim(a.level or ""),
                video = trim(a.video or ""),
                energy = nz(a.energy),
                powerpve = nz(a.powerpve),
                powerpvp = nz(a.powerpvp),
                cooldown = nz(a.cooldown),
                back = (trim(a.back or "") == "yes" or trim(a.back or "") == "true") and true or nil
            }
            if trim(a.n or "") ~= "" then
                obj.name = trim(a.n)
                obj.n = trim(a.n)
            end
            if next(desc_i18n) then
                obj.desc_i18n = desc_i18n
                obj.descPt = desc_i18n.pt
                obj.descEn = desc_i18n.en
                obj.descEs = desc_i18n.es
                obj.descPl = desc_i18n.pl
            end
            local flagsArr = parseFlags(a.flags)
            if #flagsArr > 0 then
                obj.flags = flagsArr
            end
            -- Weapon para subskill
            assignWeaponFromArg(obj, a.weapon)
            return mw.text.jsonEncode(obj)
        end
    end

    -- Se só tem refM (sem nome), retorna objeto mínimo para o JS fazer herança completa
    if n == "" and refM ~= "" then
        local obj = {
            refM = refM,
            icon = trim(a.icon or ""),
            level = trim(a.level or ""),
            video = trim(a.video or ""),
            energy = nz(a.energy),
            powerpve = nz(a.powerpve),
            powerpvp = nz(a.powerpvp),
            cooldown = nz(a.cooldown),
            back = (trim(a.back or "") == "yes" or trim(a.back or "") == "true") and true or nil
        }
        local flagsArr = parseFlags(a.flags)
        if #flagsArr > 0 then
            obj.flags = flagsArr
        end
        -- Weapon para subskill
        assignWeaponFromArg(obj, a.weapon)
        return mw.text.jsonEncode(obj)
    end

    -- Se não tem nome nem refM nem refS, retorna vazio
    if n == "" then
        return mw.text.jsonEncode({})
    end

    -- Busca descrição do módulo .lua
    local desc_i18n = {}
    local foundDesc = nil
    for _, key in ipairs(data.order or {}) do
        local sk = (data.skills or {})[key]
        if type(sk) == "table" and type(sk.subskills) == "table" then
            local sub = sk.subskills[n]
            if sub and type(sub.desc) == "table" then
                foundDesc = sub.desc
                break
            end
        end
    end
    if not foundDesc then
        foundDesc = resolveDescWithInheritance(data, n, nil)
    end
    if foundDesc then
        local langs = { "pt", "en", "es", "pl" }
        for _, code in ipairs(langs) do
            local d = foundDesc[code]
            if d and trim(d) ~= "" then
                desc_i18n[code] = colorize(d)
            end
        end
    end

    local flagsArr = parseFlags(a.flags)

    -- Objeto com nome explícito
    local obj = {
        name = n,
        icon = trim(a.icon or ""),
        level = trim(a.level or ""),
        video = trim(a.video or ""),
        energy = nz(a.energy),
        powerpve = nz(a.powerpve),
        powerpvp = nz(a.powerpvp),
        cooldown = nz(a.cooldown),
        back = (trim(a.back or "") == "yes" or trim(a.back or "") == "true") and true or nil
    }

    if refM ~= "" then
        obj.refM = refM
    end
    if #flagsArr > 0 then
        obj.flags = flagsArr
    end
    if next(desc_i18n) ~= nil then
        obj.desc_i18n = desc_i18n
        obj.descPt = desc_i18n.pt
        obj.descEn = desc_i18n.en
        obj.descEs = desc_i18n.es
        obj.descPl = desc_i18n.pl
    end

    -- Nested subs
    do
        local subsPacked = a.subs or ""
        local subsArr = {}
        for _, chunk in ipairs(collectJsonObjects(subsPacked)) do
            local ok, sub = pcall(mw.text.jsonDecode, chunk)
            if ok and type(sub) == "table" then
                ensureWeaponHydrated(sub)
                table.insert(subsArr, sub)
            end
        end
        if #subsArr > 0 then
            for _, sub in ipairs(subsArr) do
                -- BUSCA WEAPON DO MÓDULO LUA se não tem weapon ainda (para nested subs também)
                local hasWeapon = sub.weapon and (
                    (type(sub.weapon) == "table" and next(sub.weapon) ~= nil) or
                    (type(sub.weapon) == "string" and trim(sub.weapon) ~= "")
                )
                if not hasWeapon then
                    local subName = sub.name or sub.n or ""
                    if subName ~= "" then
                        for _, skillKey in ipairs(data.order or {}) do
                            local skillData = (data.skills or {})[skillKey]
                            if type(skillData) == "table" and type(skillData.subskills) == "table" then
                                local moduleSub = skillData.subskills[subName]
                                if moduleSub and type(moduleSub.weapon) == "table" and next(moduleSub.weapon) ~= nil then
                                    local weaponObj = normalizeWeaponTable(moduleSub.weapon)
                                    if weaponObj then
                                        sub.weapon = weaponObj
                                        break
                                    end
                                end
                            end
                        end
                    end
                end
                normalizeSubTree(sub)
            end
            obj.subs = subsArr
        end
    end

    -- Weapon para subskill
    -- Usa {{Weapon|...}} que retorna formato: icon~pve~pvp~cd~video~energy
    do
        assignWeaponFromArg(obj, a.weapon)

        -- Busca descrição do weapon no módulo Lua (subskills)
        for _, key in ipairs(data.order or {}) do
            local sk = (data.skills or {})[key]
            if type(sk) == "table" and type(sk.subskills) == "table" then
                local sub = sk.subskills[n]
                if sub and type(sub.weapon) == "table" then
                    local normalized = normalizeWeaponTable(sub.weapon)
                    if normalized then
                        -- Preserva campos já existentes em obj.weapon se houver
                        if obj.weapon then
                            -- Merge inteligente: só sobrescreve se o campo do módulo não estiver vazio
                            -- E só sobrescreve se o campo do template estiver vazio ou não existir
                            for k, v in pairs(normalized) do
                                -- Para video: SEMPRE preserva o do template se existir (não faz merge)
                                if k == "video" then
                                    -- Se o template tem vídeo, preserva (não sobrescreve com módulo)
                                    if obj.weapon.video and trim(obj.weapon.video) ~= "" then
                                        -- Mantém o vídeo do template, não faz nada
                                    elseif v and v ~= "" then
                                        -- Só usa do módulo se template não tem
                                        obj.weapon.video = v
                                    end
                                    -- Para icon: preserva o do template se não for Nada.png
                                elseif k == "icon" then
                                    if (not obj.weapon.icon or obj.weapon.icon == "" or obj.weapon.icon == "Nada.png") and v and v ~= "" and v ~= "Nada.png" then
                                        obj.weapon.icon = v
                                    end
                                    -- Para outros campos: só faz merge se o campo NÃO EXISTE no template (nil)
                                    -- Se o campo existe no template (mesmo que vazio), preserva o do template
                                else
                                    -- IMPORTANTE: só faz merge se o campo é nil (não existe), não se está vazio
                                    if obj.weapon[k] == nil and v ~= nil and v ~= "" then
                                        obj.weapon[k] = v
                                    end
                                end
                            end
                            -- Sempre adiciona desc_i18n do módulo se não tiver no template
                            if normalized.desc_i18n and not obj.weapon.desc_i18n then
                                obj.weapon.desc_i18n = normalized.desc_i18n
                            end
                        else
                            obj.weapon = normalized
                        end
                        -- Adiciona campos descPt, descEn, etc. para compatibilidade
                        if obj.weapon.desc_i18n then
                            obj.weapon.descPt = obj.weapon.desc_i18n.pt
                            obj.weapon.descEn = obj.weapon.desc_i18n.en
                            obj.weapon.descEs = obj.weapon.desc_i18n.es
                            obj.weapon.descPl = obj.weapon.desc_i18n.pl
                        end
                    end
                    break
                end
            end
        end

        if obj.weapon and type(obj.weapon) == "table" and next(obj.weapon) == nil then
            obj.weapon = nil
        end
    end

    -- GARANTIA FINAL: Normaliza weapon antes de serializar
    if obj.weapon then
        ensureWeaponHydrated(obj)
        -- Se weapon ainda é string ou inválido, tenta parsear
        if obj.weapon and type(obj.weapon) ~= "table" then
            local parsed = parseWeaponString(obj.weapon)
            if parsed and next(parsed) ~= nil then
                obj.weapon = parsed
            else
                obj.weapon = nil
            end
        end
        -- Remove weapon se estiver vazio
        if obj.weapon and type(obj.weapon) == "table" and next(obj.weapon) == nil then
            obj.weapon = nil
        end
    end

    return mw.text.jsonEncode(obj)
end

return p