(function () {
    'use strict';

    var root = document.getElementById('itemshop-admin-form');
    if (root) initForm(root);

    var catalog = document.getElementById('itemshop-admin-catalog');
    if (catalog) initCatalog(catalog);

    var bulk = document.getElementById('itemshop-admin-bulk');
    if (bulk) initBulk(bulk);

    function initForm(root) {
        var form = document.getElementById('itemshop-item-form');
        if (!form) return;

        var section = document.getElementById('itemshop-section');
        var index = document.getElementById('itemshop-index');
        var name = document.getElementById('itemshop-name');
        var category = document.getElementById('itemshop-category');
        var authoritativeSearch = document.getElementById('itemshop-authoritative-search');
        var authoritativeList = document.getElementById('itemshop-authoritative-list');
        var authoritativeStatus = document.getElementById('itemshop-authoritative-status');
        var width = document.getElementById('itemshop-width');
        var height = document.getElementById('itemshop-height');
        var assetImage = document.getElementById('itemshop-asset-image');
        var assetEmpty = document.getElementById('itemshop-asset-empty');
        var assetStatus = document.getElementById('itemshop-asset-status');
        var dimensionStatus = document.getElementById('itemshop-dimension-status');
        var applyDimensions = document.getElementById('itemshop-apply-dimensions');
        var codePreview = document.getElementById('itemshop-code-preview');
        var sectionLabel = document.getElementById('itemshop-section-label');
        var summaryCode = document.getElementById('itemshop-summary-code');
        var price = document.getElementById('itemshop-price');
        var salePrice = document.getElementById('itemshop-sale-price');
        var saleStart = document.getElementById('itemshop-sale-start');
        var saleEnd = document.getElementById('itemshop-sale-end');
        var stock = document.getElementById('itemshop-stock');
        var summaryPrice = document.getElementById('itemshop-summary-price');
        var summaryStock = document.getElementById('itemshop-summary-stock');
        var active = document.getElementById('itemshop-active');
        var health = document.getElementById('itemshop-form-health');
        var description = document.getElementById('itemshop-description');
        var descriptionCount = document.getElementById('itemshop-description-count');
        var resetOptions = document.getElementById('itemshop-reset-options');
        var promoInput = document.getElementById('itemshop-promo-input');
        var promoPreview = document.getElementById('itemshop-promo-preview');
        var promoPreviewImage = document.getElementById('itemshop-promo-preview-image');
        var promoPreviewLabel = document.getElementById('itemshop-promo-preview-label');
        var promoPreviewSpec = document.getElementById('itemshop-promo-preview-spec');

        var skillField = document.getElementById('itemshop-has-skill');
        var luckField = document.getElementById('itemshop-has-luck');
        var optionLevel = document.getElementById('itemshop-option-level');
        var optionHelp = document.getElementById('itemshop-option-help');
        var option380 = document.getElementById('itemshop-option-380');
        var capabilityNote = document.getElementById('itemshop-capability-note');
        var excellentProfile = document.getElementById('itemshop-excellent-profile');
        var ancient = document.getElementById('itemshop-ancient');
        var ancientHelp = document.getElementById('itemshop-ancient-help');
        var harmony = document.getElementById('itemshop-harmony');
        var harmonyHelp = document.getElementById('itemshop-harmony-help');
        var socketPanel = document.getElementById('itemshop-socket-panel');
        var socketStatus = document.getElementById('itemshop-socket-status');
        var socketCountLabel = document.getElementById('itemshop-socket-count-label');
        var socketBonus = document.getElementById('itemshop-socket-bonus');
        var socketSlots = form.querySelectorAll('[data-socket-slot]');
        var byteSummary = document.getElementById('itemshop-smart-byte-summary');

        var promoObjectUrl = '';
        var promoPendingFileSize = 0;
        var isNew = root.getAttribute('data-is-new') === '1';
        var imageBase = root.getAttribute('data-image-base') || '';
        var currency = root.getAttribute('data-currency') || '';
        var adminData = window.NEXORA_ITEMSHOP_ADMIN || {};
        var hints = adminData.hints || {};
        var sections = adminData.sections || {};
        var smart = adminData.options || {};
        var authoritativeByCode = {};
        var authoritativeByValue = {};

        if (authoritativeList) {
            Array.prototype.forEach.call(authoritativeList.options, function (option) {
                var row = {
                    section: parseInt(option.getAttribute('data-section'), 10),
                    index: parseInt(option.getAttribute('data-index'), 10),
                    name: option.getAttribute('data-name') || '',
                    width: parseInt(option.getAttribute('data-width'), 10),
                    height: parseInt(option.getAttribute('data-height'), 10),
                    compatible: option.getAttribute('data-compatible') === '1',
                    dimensionConflict: option.getAttribute('data-dimension-conflict') === '1',
                    txtWidth: parseInt(option.getAttribute('data-txt-width'), 10),
                    txtHeight: parseInt(option.getAttribute('data-txt-height'), 10),
                    excellentType: parseInt(option.getAttribute('data-excellent-type'), 10) || 0,
                    socketCapable: option.getAttribute('data-socket-capable') === '1',
                    level380: option.getAttribute('data-level380') === '1',
                    skill: parseInt(option.getAttribute('data-skill'), 10) || 0,
                    haveOption: parseInt(option.getAttribute('data-have-option'), 10) || 0,
                    value: option.value
                };
                authoritativeByCode[row.section + ':' + row.index] = row;
                authoritativeByValue[row.value] = row;
            });
        }

        var imageExtensions = ['gif', 'webp', 'png', 'jpg', 'jpeg'];
        var assetRequestId = 0;
        var detectedGrid = null;
        var touched = {
            name: !isNew || !!name.value.trim(),
            category: !isNew || category.value.trim() !== 'Destacados',
            dimensions: !isNew
        };
        var lastIdentityCode = currentCode();
        var excellent = form.querySelectorAll('[data-excellent-bit]');

        name.addEventListener('input', function () { touched.name = true; updateHealth(); });
        category.addEventListener('input', function () { touched.category = true; updateHealth(); });
        width.addEventListener('input', function () { touched.dimensions = true; updateHealth(); });
        height.addEventListener('input', function () { touched.dimensions = true; updateHealth(); });

        if (authoritativeSearch) {
            authoritativeSearch.addEventListener('change', selectAuthoritativeItem);
            authoritativeSearch.addEventListener('input', debounce(function () {
                if (authoritativeByValue[authoritativeSearch.value]) selectAuthoritativeItem();
            }, 80));
        }

        section.addEventListener('change', onIdentityChange);
        index.addEventListener('input', debounce(onIdentityChange, 120));
        applyDimensions.addEventListener('click', function () {
            if (!detectedGrid) return;
            width.value = detectedGrid.width;
            height.value = detectedGrid.height;
            touched.dimensions = true;
            updateHealth();
        });

        [price, salePrice, saleStart, saleEnd, stock, active].forEach(function (field) {
            if (!field) return;
            field.addEventListener(field.type === 'checkbox' ? 'change' : 'input', updateCommercialState);
            if (field === saleStart || field === saleEnd) field.addEventListener('change', updateCommercialState);
        });

        [skillField, luckField, optionLevel, option380, ancient, harmony, socketBonus].forEach(function (field) {
            if (field) field.addEventListener('change', function () { updateSmartSummary(); updateHealth(); });
        });

        Array.prototype.forEach.call(socketSlots, function (field, slotIndex) {
            field.addEventListener('change', function () {
                normalizeSocketSequence(slotIndex);
                updateSocketCount();
                updateSmartSummary();
                updateHealth();
            });
        });

        if (description) {
            description.addEventListener('input', updateDescriptionCount);
            updateDescriptionCount();
        }
        if (promoInput) promoInput.addEventListener('change', updatePromoPreview);
        if (promoPreviewImage) {
            promoPreviewImage.addEventListener('load', function () {
                updatePromoImageSpec(promoPendingFileSize);
                promoPendingFileSize = 0;
            });
            if (promoPreviewImage.complete && promoPreviewImage.naturalWidth > 0) updatePromoImageSpec(0);
        }

        Array.prototype.forEach.call(excellent, function (box) {
            box.addEventListener('change', function () { updateExcellentCount(); updateSmartSummary(); });
        });
        updateExcellentCount();

        if (resetOptions) {
            resetOptions.addEventListener('click', function () {
                setValue('input[name="item_level"]', '0');
                setValue('input[name="durability"]', '255');
                setChecked('input[name="has_luck"]', false);
                resetIdentityOptions();
                applySmartRules(authoritativeByCode[currentCode()] || null, false);
                updateExcellentCount();
                updateSocketCount();
                updateSmartSummary();
                updateHealth();
            });
        }

        form.addEventListener('submit', function (event) {
            updateCommercialState();
            updateHealth();
            if (!form.checkValidity()) {
                event.preventDefault();
                if (typeof form.reportValidity === 'function') form.reportValidity();
            }
        });

        updateCommercialState();
        onIdentityChange();

        function currentCode() {
            var s = parseInt(section.value, 10);
            var i = parseInt(index.value, 10);
            if (!isFinite(s)) s = 0;
            if (!isFinite(i)) i = 0;
            return s + ':' + Math.max(0, Math.min(511, i));
        }

        function onIdentityChange() {
            var s = parseInt(section.value, 10);
            var i = parseInt(index.value, 10);
            if (!isFinite(s)) s = 0;
            if (!isFinite(i)) i = 0;
            i = Math.max(0, Math.min(511, i));
            var code = s + ':' + i;
            var identityChanged = code !== lastIdentityCode;
            codePreview.textContent = code;
            summaryCode.textContent = code;
            sectionLabel.textContent = sections[s] || '';

            var official = authoritativeByCode[code];
            var hint = hints[code];
            if (official) {
                setAuthoritativeStatus(official);
                if (isNew && !touched.name) name.value = official.name;
                if (isNew && !touched.dimensions) {
                    width.value = official.width;
                    height.value = official.height;
                }
                if (isNew && !touched.category) category.value = suggestedCategory(s);
            } else if (hint && isNew) {
                setAuthoritativeStatus(null, 'El codigo no aparece en el catalogo tecnico; se conserva la referencia existente de Arsenal y requiere revision manual.');
                if (!touched.name && hint.name) name.value = hint.name;
                if (!touched.category && hint.category) category.value = hint.category;
                if (!touched.dimensions && parseInt(hint.width, 10) > 0 && parseInt(hint.height, 10) > 0) {
                    width.value = parseInt(hint.width, 10);
                    height.value = parseInt(hint.height, 10);
                }
            } else {
                setAuthoritativeStatus(null, 'El codigo no aparece en el catalogo tecnico. No publiques hasta verificar la definicion del servidor.');
                if (isNew && !touched.category) category.value = suggestedCategory(s);
            }

            if (identityChanged) resetIdentityOptions();
            applySmartRules(official || null, false);
            lastIdentityCode = code;
            probeAsset();
            updateHealth();
        }

        function selectAuthoritativeItem() {
            if (!authoritativeSearch) return;
            var row = authoritativeByValue[authoritativeSearch.value];
            if (!row) return;
            if (!row.compatible) {
                setAuthoritativeStatus(row);
                updateHealth();
                return;
            }
            section.value = String(row.section);
            index.value = String(row.index);
            name.value = row.name;
            width.value = String(row.width);
            height.value = String(row.height);
            if (isNew && !touched.category) category.value = suggestedCategory(row.section);
            touched.name = true;
            touched.dimensions = true;
            setAuthoritativeStatus(row);
            onIdentityChange();
        }

        function setAuthoritativeStatus(row, message) {
            if (!authoritativeStatus) return;
            authoritativeStatus.className = 'itemshop-authoritative-status';
            if (row) {
                authoritativeStatus.className += !row.compatible ? ' is-blocked' : (row.dimensionConflict ? ' is-warning' : ' is-valid');
                var capabilities = [];
                if (row.excellentType > 0) capabilities.push('Excellent tipo ' + row.excellentType);
                if (row.skill > 0) capabilities.push('Skill');
                if (row.haveOption > 0) capabilities.push('opcion adicional');
                if (row.socketCapable) capabilities.push('Socket');
                if (row.level380) capabilities.push('380');
                var conflict = row.dimensionConflict && isFinite(row.txtWidth) && isFinite(row.txtHeight)
                    ? ' \u00b7 Item.txt difiere: ' + row.txtWidth + '\u00d7' + row.txtHeight + '; se usa Items.xml.'
                    : '';
                authoritativeStatus.textContent = row.name + ' \u00b7 ' + row.section + ':' + row.index + ' \u00b7 Items.xml ' + row.width + '\u00d7' + row.height + ' casillas' + conflict + (capabilities.length ? ' \u00b7 ' + capabilities.join(' \u00b7 ') : '') + (row.compatible ? ' \u00b7 compatible con entrega.' : ' \u00b7 BLOQUEADO: Index > 511.');
            } else {
                authoritativeStatus.className += ' is-neutral';
                authoritativeStatus.textContent = message || 'Sin coincidencia en el catalogo tecnico.';
            }
        }

        function resetIdentityOptions() {
            setValue('#itemshop-option-level', '0');
            setChecked('#itemshop-has-skill', false);
            setChecked('#itemshop-option-380', false);
            Array.prototype.forEach.call(excellent, function (box) { box.checked = false; });
            if (ancient) ancient.value = '0';
            if (harmony) harmony.value = '0';
            Array.prototype.forEach.call(socketSlots, function (slot) { slot.value = '255'; });
            if (socketBonus) socketBonus.value = '0';
        }

        function applySmartRules(row, reset) {
            if (reset) resetIdentityOptions();
            var code = row ? row.section + ':' + row.index : currentCode();
            var capabilities = [];

            updateOptionLevel(row);
            if (skillField) {
                skillField.disabled = !row || row.skill <= 0;
                if (skillField.disabled) skillField.checked = false;
                if (!skillField.disabled) capabilities.push('Skill');
            }
            if (luckField) {
                luckField.disabled = !row || row.haveOption <= 0;
                if (luckField.disabled) luckField.checked = false;
                if (!luckField.disabled) capabilities.push('Luck');
            }
            if (option380) {
                option380.disabled = !row || !row.level380;
                if (option380.disabled) option380.checked = false;
                if (!option380.disabled) capabilities.push('380');
            }

            updateExcellentProfile(row);
            updateAncientOptions(code, row);
            updateHarmonyOptions(row);
            updateSocketOptions(row);

            var excellentProfiles = smart.excellent_profiles || {};
            var currentExcellentProfile = row ? (excellentProfiles[String(row.excellentType)] || excellentProfiles[row.excellentType]) : null;
            if (currentExcellentProfile && Array.isArray(currentExcellentProfile.options) && currentExcellentProfile.options.length) capabilities.push('Excellent');
            else if (row && row.excellentType > 0) capabilities.push('Excellent especial bloqueado');
            var ancientRow = smart.ancient && smart.ancient.by_code ? smart.ancient.by_code[code] : null;
            if (ancientRow && (ancientRow.a || ancientRow.b)) capabilities.push('Ancient');
            if (row && row.socketCapable) capabilities.push('Socket');
            else if (row && harmonyFamily(row.section)) capabilities.push('Harmony');

            if (capabilityNote) {
                capabilityNote.className = 'itemshop-capability-note' + (row ? ' is-ready' : ' is-warning');
                capabilityNote.textContent = row
                    ? 'Capacidades para ' + row.section + ':' + row.index + ': ' + (capabilities.length ? capabilities.join(' \u00b7 ') : 'sin opciones especiales declaradas') + '.'
                    : 'Sin definicion autoritativa: Arsenal mantiene las opciones especiales bloqueadas para evitar bytes no verificados.';
            }
            updateExcellentCount();
            updateSocketCount();
            updateSmartSummary();
        }

        function updateOptionLevel(row) {
            if (!optionLevel) return;
            var selected = parseInt(optionLevel.value, 10) || 0;
            clearSelect(optionLevel);
            if (row && row.haveOption > 0) {
                for (var n = 0; n <= 7; n++) appendOption(optionLevel, n, 'Nivel ' + n);
                optionLevel.value = String(Math.max(0, Math.min(7, selected)));
                optionLevel.classList.remove('itemshop-locked-control');
                if (optionHelp) optionHelp.textContent = 'Item.txt permite opcion adicional; Arsenal conserva el nivel exacto 0-7.';
            } else {
                appendOption(optionLevel, 0, 'Nivel 0 \u00b7 no compatible');
                optionLevel.value = '0';
                optionLevel.classList.add('itemshop-locked-control');
                if (optionHelp) optionHelp.textContent = 'Este codigo no declara opcion adicional en Item.txt.';
            }
        }

        function updateExcellentProfile(row) {
            var type = row ? row.excellentType : 0;
            var profiles = smart.excellent_profiles || {};
            var profile = profiles[String(type)] || profiles[type] || { name: 'Sin Excellent', options: [] };
            var byBit = {};
            (profile.options || []).forEach(function (option) { byBit[parseInt(option.bit, 10)] = option.name || ''; });
            if (excellentProfile) excellentProfile.textContent = profile.name || ('Tipo ' + type);
            Array.prototype.forEach.call(excellent, function (box, position) {
                var bit = parseInt(box.value, 10);
                var label = box.parentNode ? box.parentNode.querySelector('[data-excellent-label]') : null;
                if (label) label.textContent = byBit[bit] || ('Opcion Excellent ' + (position + 1));
                box.disabled = !row || type === 0 || !byBit[bit];
                if (box.disabled) box.checked = false;
                if (box.parentNode) box.parentNode.classList.toggle('is-disabled', box.disabled);
            });
        }

        function updateAncientOptions(code, row) {
            if (!ancient) return;
            var selected = parseInt(ancient.value, 10) || 0;
            var ancientRow = smart.ancient && smart.ancient.by_code ? smart.ancient.by_code[code] : null;
            var allowed = [0];
            clearSelect(ancient);
            appendOption(ancient, 0, 'Ninguno');
            if (row && ancientRow) {
                if (ancientRow.a) {
                    allowed.push(5, 9);
                    appendOption(ancient, 5, (ancientRow.a.name || 'Ancient A') + ' \u00b7 variante A \u00b7 +5');
                    appendOption(ancient, 9, (ancientRow.a.name || 'Ancient A') + ' \u00b7 variante A \u00b7 +10');
                }
                if (ancientRow.b) {
                    allowed.push(6, 10);
                    appendOption(ancient, 6, (ancientRow.b.name || 'Ancient B') + ' \u00b7 variante B \u00b7 +5');
                    appendOption(ancient, 10, (ancientRow.b.name || 'Ancient B') + ' \u00b7 variante B \u00b7 +10');
                }
            }
            ancient.value = allowed.indexOf(selected) !== -1 ? String(selected) : '0';
            ancient.classList.toggle('itemshop-locked-control', allowed.length === 1);
            if (ancientHelp) ancientHelp.textContent = allowed.length > 1
                ? 'Variantes permitidas por Ancient_Items.xml y Ancient_Type.xml para este codigo.'
                : 'Este codigo no tiene Ancient asociado en Ancient_Items.xml.';
        }

        function updateHarmonyOptions(row) {
            if (!harmony) return;
            var selected = parseInt(harmony.value, 10) || 0;
            clearSelect(harmony);
            appendOption(harmony, 0, 'Ninguno');
            if (!row) {
                harmony.value = '0';
                harmony.classList.add('itemshop-locked-control');
                return;
            }
            if (row.socketCapable) {
                harmony.value = '0';
                harmony.classList.add('itemshop-locked-control');
                if (harmonyHelp) harmonyHelp.textContent = 'Item Socket: el byte compartido se reserva para Socket Bonus; Harmony queda en Ninguno.';
                return;
            }
            var family = harmonyFamily(row.section);
            var groups = smart.harmony && smart.harmony[family] ? smart.harmony[family] : [];
            var allowed = [0];
            groups.forEach(function (group) {
                (group.levels || []).forEach(function (level) {
                    var raw = parseInt(level.raw, 10);
                    if (!isFinite(raw)) return;
                    allowed.push(raw);
                    appendOption(harmony, raw, level.name || ((group.name || 'Harmony') + ' \u00b7 nivel ' + (level.level || 0)));
                });
            });
            harmony.value = allowed.indexOf(selected) !== -1 ? String(selected) : '0';
            harmony.classList.toggle('itemshop-locked-control', allowed.length === 1);
            if (harmonyHelp) harmonyHelp.textContent = groups.length
                ? 'Familia ' + family + ' cargada desde los XML Harmony del servidor.'
                : 'Esta Section no tiene una familia Harmony definida en los XML cargados.';
        }

        function updateSocketOptions(row) {
            var capable = !!(row && row.socketCapable);
            var allowedRows = capable ? socketRowsForSection(row.section) : [];
            Array.prototype.forEach.call(socketSlots, function (slot) {
                var selected = parseInt(slot.value, 10);
                clearSelect(slot);
                appendOption(slot, 255, 'Sin slot');
                if (capable) {
                    appendOption(slot, 254, 'Slot vacio');
                    allowedRows.forEach(function (socketRow) {
                        appendOption(slot, socketRow.index, socketRow.name || ('Socket #' + socketRow.index));
                    });
                }
                if (capable && socketValueAllowed(selected, allowedRows)) slot.value = String(selected);
                else slot.value = '255';
                slot.classList.toggle('itemshop-locked-control', !capable);
            });

            if (socketBonus) {
                var selectedBonus = parseInt(socketBonus.value, 10) || 0;
                clearSelect(socketBonus);
                appendOption(socketBonus, 0, 'Ninguno');
                if (capable && smart.socket && Array.isArray(smart.socket.bonuses)) {
                    smart.socket.bonuses.forEach(function (bonus) {
                        if (!bonus || !isFinite(parseInt(bonus.index, 10))) return;
                        var bonusIndex = parseInt(bonus.index, 10);
                        if (bonusIndex === 50) return;
                        appendOption(socketBonus, bonusIndex, bonus.name || ('Bonus #' + bonus.index));
                    });
                }
                var bonusAllowed = selectedBonus === 0 || (capable && socketBonusAllowed(selectedBonus));
                socketBonus.value = bonusAllowed ? String(selectedBonus) : '0';
                socketBonus.classList.toggle('itemshop-locked-control', !capable);
            }

            if (socketPanel) socketPanel.classList.toggle('is-disabled', !capable);
            if (socketStatus) {
                socketStatus.className = 'label ' + (capable ? 'label-success' : 'label-default');
                socketStatus.textContent = capable ? 'Compatible' : 'No compatible';
            }
        }

        function harmonyFamily(s) {
            s = parseInt(s, 10);
            if (s >= 0 && s <= 4) return 'attack';
            if (s === 5) return 'staffs';
            if (s >= 6 && s <= 11) return 'defensive';
            return '';
        }

        function socketRowsForSection(s) {
            var rows = smart.socket && Array.isArray(smart.socket.options) ? smart.socket.options : [];
            s = parseInt(s, 10);
            return rows.filter(function (row) {
                if (!row) return false;
                var idx = parseInt(row.index, 10);
                if (idx === 254 || idx === 255) return false;
                var type = parseInt(row.type, 10);
                if (s >= 0 && s <= 5) return type === 0 || type === 2;
                if (s >= 6 && s <= 11) return type === 1 || type === 2;
                return false;
            });
        }

        function socketValueAllowed(value, rows) {
            if (value === 254 || value === 255) return true;
            for (var i = 0; i < rows.length; i++) if (parseInt(rows[i].index, 10) === value) return true;
            return false;
        }

        function socketBonusAllowed(value) {
            if (value === 0) return true;
            var rows = smart.socket && Array.isArray(smart.socket.bonuses) ? smart.socket.bonuses : [];
            for (var i = 0; i < rows.length; i++) {
                var indexValue = parseInt(rows[i].index, 10);
                if (indexValue === 50) continue;
                if (indexValue === value) return true;
            }
            return false;
        }

        function normalizeSocketSequence(changedIndex) {
            if (!socketSlots.length) return;
            var changed = parseInt(socketSlots[changedIndex].value, 10);
            if (changed !== 255) {
                for (var i = 0; i < changedIndex; i++) {
                    if (parseInt(socketSlots[i].value, 10) === 255) socketSlots[i].value = '254';
                }
            } else {
                for (var j = changedIndex + 1; j < socketSlots.length; j++) socketSlots[j].value = '255';
            }
            if (configuredSocketCount() === 0 && socketBonus) socketBonus.value = '0';
        }

        function configuredSocketCount() {
            var count = 0;
            Array.prototype.forEach.call(socketSlots, function (slot) {
                if (parseInt(slot.value, 10) !== 255) count += 1;
            });
            return count;
        }

        function socketsAreContiguous() {
            var closed = false;
            var valid = true;
            Array.prototype.forEach.call(socketSlots, function (slot) {
                var value = parseInt(slot.value, 10);
                if (value === 255) closed = true;
                else if (closed) valid = false;
            });
            return valid;
        }

        function updateSocketCount() {
            var count = configuredSocketCount();
            if (socketCountLabel) socketCountLabel.textContent = count + ' / 5 slots configurados';
        }

        function updateSmartSummary() {
            if (!byteSummary) return;
            var excCount = 0;
            Array.prototype.forEach.call(excellent, function (box) { if (box.checked) excCount += 1; });
            var parts = [];
            if (skillField && skillField.checked) parts.push('Skill');
            if (form.querySelector('input[name="has_luck"]') && form.querySelector('input[name="has_luck"]').checked) parts.push('Luck');
            var opt = optionLevel ? parseInt(optionLevel.value, 10) || 0 : 0;
            if (opt > 0) parts.push('opcion +' + opt);
            if (excCount) parts.push(excCount + ' Excellent');
            if (ancient && parseInt(ancient.value, 10) > 0) parts.push('Ancient');
            if (option380 && option380.checked) parts.push('380');
            var socketCount = configuredSocketCount();
            if (socketCount) parts.push(socketCount + ' Socket');
            else if (harmony && parseInt(harmony.value, 10) > 0) parts.push('Harmony');
            byteSummary.textContent = parts.length ? parts.join(' \u00b7 ') : 'Configuracion base, sin opciones especiales.';
        }

        function probeAsset() {
            var s = parseInt(section.value, 10);
            var i = parseInt(index.value, 10);
            if (!isFinite(s) || s < 0 || s > 15 || !isFinite(i) || i < 0 || i > 511) {
                showMissingAsset('Codigo fuera del rango soportado.');
                return;
            }
            var requestId = ++assetRequestId;
            detectedGrid = null;
            applyDimensions.disabled = true;
            applyDimensions.removeAttribute('data-width');
            applyDimensions.removeAttribute('data-height');
            tryExtension(requestId, s, i, 0);
        }

        function tryExtension(requestId, s, i, position) {
            if (requestId !== assetRequestId) return;
            if (position >= imageExtensions.length) {
                showMissingAsset('No hay asset ' + s + ':' + i + ' en el template. Verifica el codigo antes de publicar.');
                updateHealth();
                return;
            }
            var ext = imageExtensions[position];
            var url = imageBase + s + '/' + i + '.' + ext;
            var probe = new Image();
            probe.onload = function () {
                if (requestId !== assetRequestId) return;
                assetImage.src = url;
                assetImage.hidden = false;
                assetEmpty.hidden = true;
                assetStatus.textContent = 'Asset localizado \u00b7 ' + probe.naturalWidth + '\u00d7' + probe.naturalHeight + ' px';
                if (probe.naturalWidth % 32 === 0 && probe.naturalHeight % 32 === 0) {
                    var w = probe.naturalWidth / 32;
                    var h = probe.naturalHeight / 32;
                    if (w >= 1 && w <= 8 && h >= 1 && h <= 15) {
                        detectedGrid = { width: w, height: h };
                        dimensionStatus.textContent = 'Medida visual verificable: ' + w + '\u00d7' + h + ' casillas. Items.xml sigue siendo la autoridad.';
                        applyDimensions.disabled = false;
                        applyDimensions.setAttribute('data-width', w);
                        applyDimensions.setAttribute('data-height', h);
                        updateHealth();
                        return;
                    }
                }
                detectedGrid = null;
                dimensionStatus.textContent = 'La imagen existe, pero no permite deducir el tamano del baul con seguridad.';
                applyDimensions.disabled = true;
                updateHealth();
            };
            probe.onerror = function () { tryExtension(requestId, s, i, position + 1); };
            probe.src = url;
        }

        function showMissingAsset(text) {
            detectedGrid = null;
            assetImage.hidden = true;
            assetImage.removeAttribute('src');
            assetEmpty.hidden = false;
            assetStatus.textContent = 'Asset no localizado';
            dimensionStatus.textContent = text;
            applyDimensions.disabled = true;
        }

        function updateExcellentCount() {
            var count = 0;
            Array.prototype.forEach.call(excellent, function (box) { if (box.checked) count += 1; });
            var badge = document.getElementById('itemshop-excellent-count');
            if (badge) badge.textContent = count + (count === 1 ? ' seleccionada' : ' seleccionadas');
            updateHealth();
        }

        function updateDescriptionCount() {
            if (descriptionCount && description) descriptionCount.textContent = description.value.length;
        }

        function promoQuality(widthValue, heightValue) {
            var widthPx = parseInt(widthValue, 10) || 0;
            var heightPx = parseInt(heightValue, 10) || 0;
            if (widthPx < 1 || heightPx < 1) return { state: 'unknown', text: 'Resolución no disponible' };
            var ratio = widthPx / heightPx;
            var ideal = 16 / 9;
            var ratioDelta = Math.abs(ratio - ideal) / ideal;
            if (widthPx >= 1600 && heightPx >= 900 && ratioDelta <= 0.08) return { state: 'optimal', text: 'Óptima para Arsenal' };
            if (widthPx >= 1280 && heightPx >= 720 && ratioDelta <= 0.12) return { state: 'good', text: 'Buena · 1600×900 recomendado' };
            if (widthPx < 1280 || heightPx < 720) return { state: 'low', text: 'Resolución baja · usa 1600×900' };
            return { state: 'crop', text: 'Formato distinto a 16:9 · centra el sujeto' };
        }

        function updatePromoImageSpec(fileSize) {
            if (!promoPreviewImage || !promoPreviewSpec || !promoPreview) return;
            var widthPx = promoPreviewImage.naturalWidth || 0;
            var heightPx = promoPreviewImage.naturalHeight || 0;
            if (widthPx < 1 || heightPx < 1) return;
            var quality = promoQuality(widthPx, heightPx);
            var parts = [widthPx + ' × ' + heightPx + ' px', quality.text];
            if (fileSize) parts.push(Math.max(1, Math.round(fileSize / 1024)) + ' KB');
            promoPreviewSpec.textContent = parts.join(' · ');
            promoPreview.setAttribute('data-promo-quality', quality.state);
        }

        function updatePromoPreview() {
            if (!promoInput || !promoPreview || !promoPreviewImage || !promoPreviewLabel) return;
            promoInput.setCustomValidity('');
            var file = promoInput.files && promoInput.files.length ? promoInput.files[0] : null;
            if (!file) return;
            var allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
            if (allowed.indexOf(file.type) === -1) {
                promoInput.setCustomValidity('Usa una imagen JPG, PNG, GIF o WEBP.');
                if (typeof promoInput.reportValidity === 'function') promoInput.reportValidity();
                return;
            }
            if (file.size < 1 || file.size > 2 * 1024 * 1024) {
                promoInput.setCustomValidity('La imagen promocional debe pesar menos de 2 MB.');
                if (typeof promoInput.reportValidity === 'function') promoInput.reportValidity();
                return;
            }
            if (promoObjectUrl && window.URL && window.URL.revokeObjectURL) window.URL.revokeObjectURL(promoObjectUrl);
            promoObjectUrl = window.URL && window.URL.createObjectURL ? window.URL.createObjectURL(file) : '';
            promoPreviewLabel.textContent = file.name;
            if (promoPreviewSpec) promoPreviewSpec.textContent = 'Analizando resolución…';
            promoPreview.removeAttribute('data-promo-quality');
            promoPreview.hidden = false;
            if (promoObjectUrl) {
                promoPendingFileSize = file.size;
                promoPreviewImage.src = promoObjectUrl;
            }
        }

        function updateCommercialState() {
            var normal = parseInt(price.value, 10) || 0;
            var saleRaw = salePrice.value.trim();
            var sale = saleRaw === '' ? 0 : (parseInt(saleRaw, 10) || 0);
            var saleHelp = document.getElementById('itemshop-sale-help');

            salePrice.setCustomValidity('');
            saleEnd.setCustomValidity('');
            if (sale > 0 && normal > 0 && sale >= normal) {
                salePrice.setCustomValidity('El precio promocional debe ser menor que el precio normal.');
                if (saleHelp) saleHelp.textContent = 'La promocion debe ser menor que el precio normal.';
            } else if (sale > 0 && normal > 0) {
                var discount = Math.max(1, Math.round((1 - (sale / normal)) * 100));
                if (saleHelp) saleHelp.textContent = 'Descuento configurado: -' + discount + '%.';
            } else if (saleHelp) {
                saleHelp.textContent = 'Vacio = sin descuento.';
            }
            if (saleStart.value && saleEnd.value && saleEnd.value <= saleStart.value) {
                saleEnd.setCustomValidity('La promocion debe terminar despues de iniciar.');
            }

            summaryPrice.textContent = formatNumber(normal) + (currency ? ' ' + currency : '');
            var stockValue = parseInt(stock.value, 10);
            summaryStock.textContent = stockValue < 0 ? 'Ilimitado' : formatNumber(isFinite(stockValue) ? stockValue : 0);
            updateHealth();
        }

        function updateHealth() {
            if (!health) return;
            var issues = [];
            var warnings = [];
            var s = parseInt(section.value, 10);
            var i = parseInt(index.value, 10);
            var w = parseInt(width.value, 10);
            var h = parseInt(height.value, 10);
            var official = authoritativeByCode[s + ':' + i];

            if (!name.value.trim()) issues.push('falta el nombre');
            if (!category.value.trim()) issues.push('falta la categoria');
            if (!isFinite(s) || s < 0 || s > 15 || !isFinite(i) || i < 0 || i > 511) issues.push('codigo fuera de rango');
            if (!isFinite(w) || w < 1 || w > 8 || !isFinite(h) || h < 1 || h > 15) issues.push('medida de baul invalida');
            if (official && !official.compatible) issues.push('el catalogo usa un Index fuera del rango DbVersion 3 confirmado');
            if (official && (official.width !== w || official.height !== h)) issues.push('medida distinta a Items.xml (' + official.width + '\u00d7' + official.height + ')');
            if (!official) warnings.push('codigo no confirmado por el catalogo tecnico');
            if (official && official.dimensionConflict) warnings.push('Items.xml e Item.txt discrepan en tamano; se usa Items.xml');

            if (official) {
                if (skillField && skillField.checked && official.skill <= 0) issues.push('Skill no permitido para este codigo');
                if (luckField && luckField.checked && official.haveOption <= 0) issues.push('Luck no permitido para este codigo');
                if (optionLevel && parseInt(optionLevel.value, 10) > 0 && official.haveOption <= 0) issues.push('opcion adicional no permitida');
                if (option380 && option380.checked && !official.level380) issues.push('opcion 380 no permitida');
                var selectedExcellent = 0;
                Array.prototype.forEach.call(excellent, function (box) { if (box.checked) selectedExcellent += 1; });
                if (selectedExcellent && official.excellentType === 0) issues.push('Excellent no permitido');
                if (official.socketCapable) {
                    if (harmony && parseInt(harmony.value, 10) !== 0) issues.push('Harmony debe quedar en Ninguno para un item Socket');
                    if (!socketsAreContiguous()) issues.push('los slots Socket no son contiguos');
                    if (socketBonus && parseInt(socketBonus.value, 10) !== 0 && configuredSocketCount() === 0) issues.push('Socket Bonus requiere al menos un slot');
                } else {
                    if (configuredSocketCount() !== 0) issues.push('el item no admite Socket');
                    if (socketBonus && parseInt(socketBonus.value, 10) !== 0) issues.push('Socket Bonus no permitido');
                }
            }

            if (salePrice.validationMessage) issues.push('promocion invalida');
            if (saleEnd.validationMessage) issues.push('fechas de promocion invalidas');
            if (!assetImage.src || assetImage.hidden) warnings.push('asset del cliente no localizado');
            if (detectedGrid && (detectedGrid.width !== w || detectedGrid.height !== h)) warnings.push('la imagen sugiere otra medida; Items.xml mantiene prioridad');
            if (active.checked && warnings.length) warnings.push('esta marcado para publicacion');

            health.className = 'alert';
            if (issues.length) {
                health.className += ' alert-danger';
                health.textContent = 'Corregir antes de guardar: ' + issues.join(' \u00b7 ') + '.';
            } else if (warnings.length) {
                health.className += ' alert-warning';
                health.textContent = 'Revision recomendada: ' + warnings.join(' \u00b7 ') + '.';
            } else {
                health.className += ' alert-success';
                health.textContent = 'Configuracion coherente: identidad y opciones coinciden con los catalogos tecnicos. El servidor vuelve a validarlo al guardar.';
            }
        }

        function clearSelect(select) {
            while (select.firstChild) select.removeChild(select.firstChild);
        }

        function appendOption(select, value, label) {
            var option = document.createElement('option');
            option.value = String(value);
            option.textContent = label;
            select.appendChild(option);
        }

        function suggestedCategory(s) {
            if (s >= 0 && s <= 5) return 'Armas';
            if (s >= 6 && s <= 11) return 'Equipamiento';
            if (s === 12) return 'Alas y orbes';
            if (s === 13) return 'Accesorios';
            if (s === 14) return 'Consumibles';
            return 'Especiales';
        }

        function setValue(selector, value) {
            var field = form.querySelector(selector);
            if (field) field.value = value;
        }

        function setChecked(selector, value) {
            var field = form.querySelector(selector);
            if (field) field.checked = value;
        }
    }

    function initCatalog(root) {
        var search = document.getElementById('itemshop-catalog-search');
        var category = document.getElementById('itemshop-catalog-category');
        var state = document.getElementById('itemshop-catalog-state');
        var rows = root.querySelectorAll('[data-itemshop-row]');
        var checks = root.querySelectorAll('[data-itemshop-catalog-check]');
        var count = document.getElementById('itemshop-catalog-count');
        var form = document.getElementById('itemshop-catalog-bulk-form');
        var selectedInput = document.getElementById('itemshop-catalog-selected-input');
        var selectedInputs = root.querySelectorAll('[data-itemshop-selected-input]');
        var editForm = document.getElementById('itemshop-bulk-edit-form');
        var promotionMode = document.getElementById('itemshop-bulk-edit-promotion-mode');
        var selectedCount = document.getElementById('itemshop-catalog-selected-count');
        var selectVisible = document.getElementById('itemshop-catalog-select-visible');
        var clearSelection = document.getElementById('itemshop-catalog-clear-selection');
        if (!search || !category || !state) return;

        var selected = Object.create(null);
        String(root.getAttribute('data-selected-ids') || '').split(',').forEach(function (id) {
            id = String(id || '').trim();
            if (id) selected[id] = true;
        });
        search.addEventListener('input', debounce(filter, 90));
        category.addEventListener('change', filter);
        state.addEventListener('change', filter);

        Array.prototype.forEach.call(checks, function (check) {
            check.addEventListener('change', function () {
                var id = String(check.getAttribute('data-id') || '').trim();
                if (!id) return;
                if (check.checked) selected[id] = true;
                else delete selected[id];
                syncSelection();
            });
        });

        if (selectVisible) selectVisible.addEventListener('click', function () {
            Array.prototype.forEach.call(rows, function (row) {
                if (row.classList.contains('itemshop-row-hidden')) return;
                var check = row.querySelector('[data-itemshop-catalog-check]');
                if (!check) return;
                check.checked = true;
                var id = String(check.getAttribute('data-id') || '').trim();
                if (id) selected[id] = true;
            });
            syncSelection();
        });

        if (clearSelection) clearSelection.addEventListener('click', function () {
            selected = Object.create(null);
            Array.prototype.forEach.call(checks, function (check) { check.checked = false; });
            syncSelection();
        });

        if (form) form.addEventListener('submit', function (event) {
            syncSelection();
            if (!selectedInput || !selectedInput.value) {
                event.preventDefault();
                window.alert('Selecciona al menos una configuración del catálogo.');
                return;
            }
            var action = form.querySelector('[name="bulk_action"]');
            var verb = action && action.value === 'hide' ? 'ocultar' : 'publicar';
            var total = Object.keys(selected).length;
            if (!window.confirm('¿' + verb.charAt(0).toUpperCase() + verb.slice(1) + ' ' + total + ' configuraciones seleccionadas?\n\nLa acción se aplicará únicamente al reino activo.')) {
                event.preventDefault();
            }
        });

        if (promotionMode && editForm) {
            var promotionFields = editForm.querySelectorAll('[name="edit_sale_price_wc"],[name="edit_sale_start"],[name="edit_sale_end"]');
            var syncPromotionFields = function () {
                var enabled = promotionMode.value === 'set';
                Array.prototype.forEach.call(promotionFields, function (field) { field.disabled = !enabled; });
            };
            promotionMode.addEventListener('change', syncPromotionFields);
            syncPromotionFields();
        }

        if (editForm) editForm.addEventListener('submit', function (event) {
            syncSelection();
            var totalSelected = Object.keys(selected).length;
            if (!totalSelected) {
                event.preventDefault();
                window.alert('Selecciona al menos una configuración del catálogo para editar.');
                return;
            }
            if (totalSelected > 500) {
                event.preventDefault();
                window.alert('La edición masiva admite hasta 500 configuraciones por operación. Reduce la selección.');
            }
        });

        filter();
        syncSelection();

        function filter() {
            var term = normalize(search.value);
            var categoryValue = normalize(category.value);
            var stateValue = state.value;
            var visible = 0;
            Array.prototype.forEach.call(rows, function (row) {
                var text = normalize(row.getAttribute('data-search') || '');
                var rowCategory = normalize(row.getAttribute('data-category') || '');
                var visibility = row.getAttribute('data-visibility') || '';
                var stockState = row.getAttribute('data-stock-state') || '';
                var stateMatch = !stateValue || (stateValue === 'soldout' ? stockState === 'soldout' : visibility === stateValue);
                var show = (!term || text.indexOf(term) !== -1) && (!categoryValue || rowCategory === categoryValue) && stateMatch;
                row.classList.toggle('itemshop-row-hidden', !show);
                if (show) visible += 1;
            });
            if (count) count.textContent = visible + ' de ' + rows.length + ' items';
        }

        function syncSelection() {
            var ids = Object.keys(selected).sort(function (a, b) { return parseInt(a, 10) - parseInt(b, 10); });
            if (selectedInput) selectedInput.value = ids.join(',');
            Array.prototype.forEach.call(selectedInputs, function (input) { input.value = ids.join(','); });
            if (selectedCount) selectedCount.textContent = String(ids.length);
            Array.prototype.forEach.call(checks, function (check) {
                var id = String(check.getAttribute('data-id') || '').trim();
                check.checked = !!selected[id];
            });
        }
    }

    function initBulk(root) {
        var dataNode = document.getElementById('itemshop-bulk-data');
        var form = document.getElementById('itemshop-bulk-form');
        var hidden = document.getElementById('itemshop-bulk-selected-input');
        var grid = document.getElementById('itemshop-bulk-grid');
        var search = document.getElementById('itemshop-bulk-search');
        var section = document.getElementById('itemshop-bulk-section');
        var capability = document.getElementById('itemshop-bulk-capability');
        var selectedOnly = document.getElementById('itemshop-bulk-selected-only');
        var preset = document.getElementById('itemshop-bulk-preset');
        var savedPreset = document.getElementById('itemshop-bulk-saved-preset');
        var selectVisible = document.getElementById('itemshop-bulk-select-visible');
        var clear = document.getElementById('itemshop-bulk-clear');
        var selectedCount = document.getElementById('itemshop-bulk-selected-count');
        var visibleCount = document.getElementById('itemshop-bulk-visible-count');
        var prev = document.getElementById('itemshop-bulk-prev');
        var next = document.getElementById('itemshop-bulk-next');
        var pageLabel = document.getElementById('itemshop-bulk-page-label');
        var previewButton = document.getElementById('itemshop-bulk-preview-button');
        var presetSocket = document.getElementById('itemshop-bulk-preset-socket');
        if (!dataNode || !form || !hidden || !grid || !search || !section || !capability || !preset) return;

        var items = [];
        try { items = JSON.parse(dataNode.textContent || '[]'); } catch (e) { items = []; }
        if (!Array.isArray(items)) items = [];
        var pageSize = Math.max(12, parseInt(root.getAttribute('data-page-size'), 10) || 48);
        var renderBase = root.getAttribute('data-render-base') || '';
        var page = 1;
        var maxSelection = 500;
        var selected = Object.create(null);
        String(hidden.value || '').split(',').forEach(function (code) {
            code = String(code || '').trim();
            if (code) selected[code] = true;
        });

        search.addEventListener('input', debounce(function () { page = 1; render(); }, 80));
        section.addEventListener('change', function () { page = 1; render(); });
        capability.addEventListener('change', function () { page = 1; render(); });
        if (selectedOnly) selectedOnly.addEventListener('change', function () { page = 1; render(); });
        preset.addEventListener('change', function () {
            updatePresetSummary();
            Object.keys(selected).forEach(function (code) {
                var item = findByCode(code);
                if (item && isBlocked(item)) delete selected[code];
            });
            page = 1;
            syncSelected();
            render();
        });
        if (savedPreset) savedPreset.addEventListener('change', function () {
            var option = savedPreset.options[savedPreset.selectedIndex];
            if (!option || !option.value) return;
            var presetKey = option.getAttribute('data-preset-key') || '';
            if (presetKey) preset.value = presetKey;
            var fieldMap = {
                price_wc: option.getAttribute('data-price'),
                stock: option.getAttribute('data-stock'),
                max_per_account: option.getAttribute('data-limit'),
                sort_order: option.getAttribute('data-order'),
                category_override: option.getAttribute('data-category') || ''
            };
            Object.keys(fieldMap).forEach(function (name) {
                var field = form.querySelector('[name="' + name + '"]');
                if (field) field.value = fieldMap[name] == null ? '' : fieldMap[name];
            });
            preset.dispatchEvent(new Event('change', { bubbles: true }));
        });
        if (selectVisible) selectVisible.addEventListener('click', selectFiltered);
        if (clear) clear.addEventListener('click', function () { selected = Object.create(null); syncSelected(); render(); });
        if (prev) prev.addEventListener('click', function () { if (page > 1) { page -= 1; render(); } });
        if (next) next.addEventListener('click', function () {
            var filtered = filteredItems();
            var pages = Math.max(1, Math.ceil(filtered.length / pageSize));
            if (page < pages) { page += 1; render(); }
        });

        grid.addEventListener('change', function (event) {
            var input = event.target;
            if (!input || !input.matches('input[data-itemshop-bulk-check]')) return;
            var code = input.getAttribute('data-code') || '';
            if (!code) return;
            if (input.checked) {
                if (Object.keys(selected).length >= maxSelection && !selected[code]) {
                    input.checked = false;
                    window.alert('Cada lote admite hasta ' + maxSelection + ' ítems. Divide una selección mayor en varios lotes.');
                    return;
                }
                selected[code] = true;
            } else {
                delete selected[code];
            }
            syncSelected();
            render();
        });

        grid.addEventListener('click', function (event) {
            if (event.target.closest('input,button,a,label')) return;
            var card = event.target.closest('[data-itemshop-bulk-card]');
            if (!card || card.classList.contains('is-disabled')) return;
            var input = card.querySelector('input[data-itemshop-bulk-check]');
            if (!input || input.disabled) return;
            input.checked = !input.checked;
            input.dispatchEvent(new Event('change', { bubbles: true }));
        });

        form.addEventListener('submit', function (event) {
            var submitter = event.submitter || document.activeElement;
            var actionName = submitter && submitter.name ? submitter.name : '';
            if (actionName !== 'preview_bulk' && actionName !== 'import_bulk') return;
            syncSelected();
            if (!hidden.value) {
                event.preventDefault();
                window.alert('Selecciona al menos un ítem del catálogo técnico.');
            }
        });

        function findByCode(code) {
            for (var i = 0; i < items.length; i += 1) if (items[i].code === code) return items[i];
            return null;
        }

        function presetUsesExcellent() {
            return preset.value === 'full_excellent_0' || preset.value === 'full_excellent_socket5_0';
        }

        function presetOpensSockets() {
            return preset.value === 'full_excellent_socket5_0' || preset.value === 'skill_luck_socket5_0';
        }

        function updatePresetSummary() {
            if (presetSocket) presetSocket.textContent = presetOpensSockets() ? '5 slots si corresponde' : 'Ninguno';
        }

        function isBlocked(item) {
            if (!item || !item.compatible || item.existing_id) return true;
            return presetUsesExcellent() && item.excellent_pending;
        }

        function filteredItems() {
            var term = normalize(search.value);
            var sectionValue = section.value;
            var capabilityValue = capability.value;
            return items.filter(function (item) {
                if (selectedOnly && selectedOnly.checked && !selected[item.code]) return false;
                if (sectionValue !== '' && String(item.section) !== sectionValue) return false;
                if (term) {
                    var haystack = normalize(item.code + ' ' + item.name + ' ' + item.category + ' ' + item.section_label);
                    if (haystack.indexOf(term) === -1) return false;
                }
                if (capabilityValue === 'excellent' && !(item.excellent_count > 0)) return false;
                if (capabilityValue === 'skill' && !item.skill) return false;
                if (capabilityValue === 'luck' && !item.luck) return false;
                if (capabilityValue === 'socket' && !item.socket) return false;
                if (capabilityValue === '380' && !item.level380) return false;
                return true;
            });
        }

        function selectFiltered() {
            var filtered = filteredItems();
            var current = Object.keys(selected).length;
            var added = 0;
            for (var i = 0; i < filtered.length && current < maxSelection; i += 1) {
                var item = filtered[i];
                if (isBlocked(item) || selected[item.code]) continue;
                selected[item.code] = true;
                current += 1;
                added += 1;
            }
            if (current >= maxSelection && filtered.length > added) window.alert('La selección llegó al límite de ' + maxSelection + ' ítems por lote.');
            syncSelected();
            render();
        }

        function syncSelected() {
            var codes = Object.keys(selected).sort(function (a, b) {
                var aa = a.split(':').map(Number);
                var bb = b.split(':').map(Number);
                return aa[0] === bb[0] ? aa[1] - bb[1] : aa[0] - bb[0];
            });
            hidden.value = codes.join(',');
            if (selectedCount) selectedCount.textContent = String(codes.length);
        }

        function cardHtml(item) {
            var blocked = isBlocked(item);
            var checked = !!selected[item.code];
            var classes = ['itemshop-bulk-card'];
            if (checked) classes.push('is-selected');
            if (blocked) classes.push('is-disabled');
            if (item.existing_id) classes.push('is-existing');
            var badges = [];
            if (item.skill) badges.push('<span>Skill</span>');
            if (item.luck) badges.push('<span>Luck</span>');
            if (item.excellent_count > 0) badges.push('<span>Excellent ' + item.excellent_count + '/6</span>');
            if (item.socket) badges.push('<span>Socket</span>');
            if (item.level380) badges.push('<span>380</span>');
            var status = 'Disponible para el lote';
            if (item.existing_id) status = 'Ya en catálogo #' + item.existing_id;
            else if (!item.compatible) status = 'No compatible con encoder';
            else if (presetUsesExcellent() && item.excellent_pending) status = 'Excellent especial pendiente';
            else if (presetOpensSockets() && item.socket) status = 'Preset abrirá 5 slots Socket';
            else if (presetOpensSockets() && !item.socket) status = 'Sin Socket técnico · se conserva sin slots';
            else if (item.dimension_conflict) status = 'Medidas validadas por Warehouse Editor';
            return '' +
                '<article class="' + classes.join(' ') + '" data-itemshop-bulk-card data-code="' + escapeHtml(item.code) + '">' +
                    '<label class="itemshop-bulk-check"><input type="checkbox" data-itemshop-bulk-check data-code="' + escapeHtml(item.code) + '"' + (checked ? ' checked' : '') + (blocked ? ' disabled' : '') + '><span></span></label>' +
                    '<div class="itemshop-bulk-visual"><img loading="lazy" decoding="async" src="' + escapeHtml(renderBase + item.section + '/' + item.index + '.webp') + '" alt=""><em>' + escapeHtml(item.code) + '</em></div>' +
                    '<div class="itemshop-bulk-card-body">' +
                        '<div class="itemshop-bulk-code">' + escapeHtml(item.code) + ' · ' + escapeHtml(item.section_label) + '</div>' +
                        '<strong>' + escapeHtml(item.name) + '</strong>' +
                        '<small>' + escapeHtml(item.category) + ' · ' + item.width + '×' + item.height + '</small>' +
                        '<div class="itemshop-bulk-badges">' + (badges.length ? badges.join('') : '<span>Base</span>') + '</div>' +
                        '<div class="itemshop-bulk-card-status">' + escapeHtml(status) + '</div>' +
                    '</div>' +
                '</article>';
        }

        function render() {
            var filtered = filteredItems();
            var pages = Math.max(1, Math.ceil(filtered.length / pageSize));
            if (page > pages) page = pages;
            var start = (page - 1) * pageSize;
            var pageItems = filtered.slice(start, start + pageSize);
            grid.innerHTML = pageItems.length ? pageItems.map(cardHtml).join('') : '<div class="itemshop-bulk-empty">No hay ítems que coincidan con estos filtros.</div>';
            Array.prototype.forEach.call(grid.querySelectorAll('.itemshop-bulk-visual img'), function (img) {
                img.addEventListener('error', function () {
                    var visual = img.closest('.itemshop-bulk-visual');
                    if (visual) visual.classList.add('is-missing');
                    img.remove();
                }, { once: true });
            });
            if (visibleCount) visibleCount.textContent = String(filtered.length);
            if (pageLabel) pageLabel.textContent = 'Página ' + page + ' de ' + pages + (filtered.length ? ' · ' + (start + 1) + '–' + Math.min(start + pageSize, filtered.length) : '');
            if (prev) prev.disabled = page <= 1;
            if (next) next.disabled = page >= pages;
            syncSelected();
        }

        updatePresetSummary();
        syncSelected();
        render();
    }

    function escapeHtml(value) {
        return String(value == null ? '' : value)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#039;');
    }

    function normalize(value) {
        return String(value || '').toLowerCase().replace(/\s+/g, ' ').trim();
    }

    function formatNumber(value) {
        try { return Number(value || 0).toLocaleString(); } catch (e) { return String(value || 0); }
    }

    function debounce(fn, wait) {
        var timer = null;
        return function () {
            var args = arguments;
            var ctx = this;
            clearTimeout(timer);
            timer = setTimeout(function () { fn.apply(ctx, args); }, wait);
        };
    }
}());
