/**
 * Entity Selector - Utilities Module
 * Helper functions: escape, validation, icons, search history
 * @partial _utils.js (must be loaded first)
 *
 * EXTRACTION SOURCE: assets/js/admin/entity-selector.js
 * Lines: 7552-7570 (escapeHtml, escapeAttr)
 *        7577-7590 (getEntityTypeLabel)
 *        6289-6350 (validate, showValidationError, clearValidationError)
 *        7115-7137 (showRangeInputError)
 *        7728-7745 (getBlockMode, isBlockSingleMode)
 *        7707-7723 (getCurrentSingleSelection)
 *        5411-5467 (search history methods)
 */

(function($) {
    'use strict';

    // Create mixin namespace
    window._EntitySelectorMixins = window._EntitySelectorMixins || {};

    // ---------------------------------------------------------------
    // Icon framework detection & FA4 mapping (module-level singleton)
    // ---------------------------------------------------------------
    var _iconMode = null;

    /**
     * Material Icons → FontAwesome 4 class mapping.
     * FA4 uses class-based icons (icon-name), Material uses text content.
     */
    var FA4_MAP = {
        'account_tree':             'icon-sitemap',
        'add':                      'icon-plus',
        'add_box':                  'icon-plus-square',
        'arrow_downward':           'icon-sort-desc',
        'arrow_drop_down':          'icon-caret-down',
        'arrow_right':              'icon-chevron-right',
        'arrow_upward':             'icon-sort-asc',
        'block':                    'icon-ban',
        'brush':                    'icon-paint-brush',
        'business':                 'icon-building',
        'check':                    'icon-check',
        'check_box':                'icon-check-square',
        'check_box_outline_blank':  'icon-square-o',
        'check_circle':             'icon-check-circle',
        'close':                    'icon-times',
        'delete':                   'icon-trash',
        'description':              'icon-file-text',
        'error':                    'icon-exclamation-circle',
        'event':                    'icon-calendar',
        'event_busy':               'icon-calendar-times-o',
        'expand_less':              'icon-chevron-up',
        'expand_more':              'icon-chevron-down',
        'filter_list':              'icon-filter',
        'flag':                     'icon-flag',
        'folder':                   'icon-folder',
        'folder_open':              'icon-folder-open',
        'indeterminate_check_box':  'icon-minus-square',
        'info':                     'icon-info-circle',
        'inventory_2':              'icon-archive',
        'label':                    'icon-tag',
        'language':                 'icon-globe',
        'lightbulb':                'icon-lightbulb-o',
        'list':                     'icon-list',
        'list_alt':                 'icon-list-alt',
        'local_shipping':           'icon-truck',
        'lock':                     'icon-lock',
        'my_location':              'icon-crosshairs',
        'open_in_full':             'icon-expand',
        'payments':                 'icon-credit-card',
        'progress_activity':        'icon-circle-o-notch',
        'schedule':                 'icon-clock-o',
        'search':                   'icon-search',
        'shopping_cart':            'icon-shopping-cart',
        'shuffle':                  'icon-random',
        'sort':                     'icon-sort',
        'sort_by_alpha':            'icon-sort-alpha-asc',
        'star':                     'icon-star',
        'sync':                     'icon-refresh',
        'tune':                     'icon-sliders',
        'visibility':               'icon-eye',
        'warning':                  'icon-warning',
        'widgets':                  'icon-th-large'
    };

    /**
     * Reverse map: FontAwesome 4 class → Material Icons name.
     * Built once lazily from FA4_MAP + extra mappings for FA4 names
     * that don't appear as values in FA4_MAP.
     */
    var _REVERSE_FA4_MAP = null;

    var EXTRA_REVERSE_MAPPINGS = {
        'icon-cube':          'inventory',
        'icon-folder-o':      'folder',
        'icon-file-text-o':   'description',
        'icon-briefcase':     'work',
        'icon-user':          'person',
        'icon-users':         'group',
        'icon-money':         'payments',
        'icon-tasks':         'checklist',
        'icon-calculator':    'calculate',
        'icon-asterisk':      'star',
        'icon-bar-chart':     'bar_chart',
        'icon-cogs':          'settings',
        'icon-cog':           'settings',
        'icon-tags':          'label',
        'icon-list-ul':       'list',
        'icon-th':            'grid_view',
        'icon-certificate':   'verified',
        'icon-power-off':     'power_settings_new',
        'icon-circle-o':      'radio_button_unchecked'
    };

    function getReverseFa4Map() {
        if (_REVERSE_FA4_MAP !== null) return _REVERSE_FA4_MAP;
        _REVERSE_FA4_MAP = {};
        // Invert FA4_MAP: value → key
        for (var material in FA4_MAP) {
            if (FA4_MAP.hasOwnProperty(material)) {
                var fa4Class = FA4_MAP[material];
                if (!_REVERSE_FA4_MAP[fa4Class]) {
                    _REVERSE_FA4_MAP[fa4Class] = material;
                }
            }
        }
        // Merge extras
        for (var fa4 in EXTRA_REVERSE_MAPPINGS) {
            if (EXTRA_REVERSE_MAPPINGS.hasOwnProperty(fa4) && !_REVERSE_FA4_MAP[fa4]) {
                _REVERSE_FA4_MAP[fa4] = EXTRA_REVERSE_MAPPINGS[fa4];
            }
        }
        return _REVERSE_FA4_MAP;
    }

    /**
     * Normalize an icon name — handles both Material and FA4 class names.
     * @param {string} name - Icon name (Material or FA4 format)
     * @param {string} mode - 'material' or 'fa4'
     * @returns {object} { name: string, extra: string, rawFa4: boolean }
     */
    function normalizeIconName(name, mode) {
        var extra = '';
        var rawFa4 = false;

        if (name.indexOf('icon-') === 0) {
            // FA4 class name input — may have extra classes (e.g. "icon-power-off text-success")
            var spaceIdx = name.indexOf(' ');
            var fa4Class = (spaceIdx !== -1) ? name.substring(0, spaceIdx) : name;
            if (spaceIdx !== -1) extra = name.substring(spaceIdx + 1);

            if (mode === 'material') {
                var reverseMap = getReverseFa4Map();
                var materialName = reverseMap[fa4Class];
                if (materialName) {
                    return { name: materialName, extra: extra, rawFa4: false };
                }
                // Fallback: strip 'icon-' and convert hyphens to underscores
                var fallback = fa4Class.substring(5).replace(/-/g, '_');
                return { name: fallback, extra: extra, rawFa4: false };
            }

            // FA4 mode + FA4 input — use as-is
            return { name: fa4Class, extra: extra, rawFa4: true };
        }

        // Material Icons name — pass through
        return { name: name, extra: extra, rawFa4: false };
    }

    /**
     * Detect icon framework: 'material' (PS 8+/9+) or 'fa4' (PS 1.6/1.7).
     * Checks PHP-set data attribute first, falls back to font detection.
     */
    function detectIconMode() {
        if (_iconMode !== null) return _iconMode;

        // 1. PHP sets data-icon-mode on the wrapper
        var $w = $('.entity-selector-trait[data-icon-mode], .target-conditions-trait[data-icon-mode]').first();
        if ($w.length && $w.data('icon-mode')) {
            _iconMode = $w.data('icon-mode');
            return _iconMode;
        }

        // 2. Fallback: probe whether Material Icons font is loaded
        var test = document.createElement('i');
        test.className = 'material-icons';
        test.style.cssText = 'position:absolute;left:-9999px;top:-9999px;font-size:16px;pointer-events:none';
        test.textContent = 'check';
        (document.body || document.documentElement).appendChild(test);
        var family = (window.getComputedStyle(test).fontFamily || '').toLowerCase();
        test.parentNode.removeChild(test);

        _iconMode = (family.indexOf('material') !== -1) ? 'material' : 'fa4';
        return _iconMode;
    }

    // Utility functions mixin
    window._EntitySelectorMixins.utils = {

        /**
         * Debounce function - delays execution until after wait milliseconds
         * @param {Function} func - Function to debounce
         * @param {number} wait - Milliseconds to wait
         * @returns {Function} Debounced function
         */
        debounce: function(func, wait) {
            var timeout;
            return function() {
                var context = this;
                var args = arguments;
                clearTimeout(timeout);
                timeout = setTimeout(function() {
                    func.apply(context, args);
                }, wait);
            };
        },

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

        escapeAttr: function(str) {
            if (str === null || str === undefined) return '';
            return String(str)
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#039;');
        },

        /**
         * Icon helper — returns HTML for an icon that works on PS 1.6 through 9.x.
         * Accepts both Material Icons names and FA4 class names as input.
         * Automatically uses Material Icons (PS 8+/9+) or FontAwesome 4 (PS 1.6/1.7).
         *
         * @param {string} name - Icon name (Material or FA4 format, e.g. 'search', 'icon-cube')
         * @param {string} [extraClass] - Additional CSS class(es) (e.g. 'es-spin', 'method-trigger-icon')
         * @returns {string} HTML string for an <i> element
         */
        esIcon: function(name, extraClass) {
            var mode = detectIconMode();
            var normalized = normalizeIconName(name, mode);
            var iconName = normalized.name;
            // Merge extra classes from normalization
            if (normalized.extra) {
                extraClass = extraClass ? extraClass + ' ' + normalized.extra : normalized.extra;
            }

            if (mode === 'material') {
                var cls = 'material-icons es-icon';
                if (extraClass) cls += ' ' + extraClass;
                return '<i class="' + cls + '">' + iconName + '</i>';
            }
            // FA4 mode
            if (normalized.rawFa4) {
                // Input was already an FA4 class — use directly
                var cls = iconName + ' es-icon';
            } else {
                // Input was Material — map to FA4
                var cls = (FA4_MAP[iconName] || 'icon-circle') + ' es-icon';
            }
            if (extraClass) cls += ' ' + extraClass;
            return '<i class="' + cls + '"></i>';
        },

        /**
         * Update an existing <i> icon element to show a different icon.
         * Handles both Material Icons and FA4 modes.
         * Accepts both Material Icons names and FA4 class names as input.
         *
         * @param {jQuery} $el - The <i> element to update
         * @param {string} name - Icon name (Material or FA4 format)
         * @param {string} [extraClass] - Additional CSS class(es) to preserve
         */
        esIconUpdate: function($el, name, extraClass) {
            var mode = detectIconMode();
            var normalized = normalizeIconName(name, mode);
            var iconName = normalized.name;
            if (normalized.extra) {
                extraClass = extraClass ? extraClass + ' ' + normalized.extra : normalized.extra;
            }

            if (mode === 'material') {
                var cls = 'material-icons es-icon';
                if (extraClass) cls += ' ' + extraClass;
                $el.attr('class', cls).text(iconName);
            } else {
                if (normalized.rawFa4) {
                    var cls = iconName + ' es-icon';
                } else {
                    var cls = (FA4_MAP[iconName] || 'icon-circle') + ' es-icon';
                }
                if (extraClass) cls += ' ' + extraClass;
                $el.attr('class', cls).text('');
            }
        },

        getEntityTypeIcon: function(entityType) {
            var icons = {
                'products': 'shopping_cart',
                'categories': 'folder_open',
                'manufacturers': 'business',
                'suppliers': 'local_shipping',
                'attributes': 'list_alt',
                'features': 'label',
                'cms': 'description',
                'cms_categories': 'folder'
            };
            return icons[entityType] || 'widgets';
        },

        getEntityTypeLabel: function(entityType) {
            var trans = this.config.trans || {};
            var labels = {
                'products': trans.product || 'Product',
                'categories': trans.category || 'Category',
                'manufacturers': trans.manufacturer || 'Manufacturer',
                'suppliers': trans.supplier || 'Supplier',
                'attributes': trans.attribute || 'Attribute',
                'features': trans.feature || 'Feature',
                'cms': trans.cms_page || 'CMS Page',
                'cms_categories': trans.cms_category || 'CMS Category'
            };
            return labels[entityType] || entityType;
        },

        validate: function() {
            var isRequired = this.$wrapper.data('required') === 1 || this.$wrapper.data('required') === '1';
            if (!isRequired) return true;

            var hasData = false;
            this.$wrapper.find('.target-block').each(function() {
                if ($(this).find('.selection-group').length > 0) {
                    hasData = true;
                    return false;
                }
            });

            if (!hasData) {
                this.showValidationError();
                return false;
            }

            this.clearValidationError();
            return true;
        },

        showValidationError: function() {
            this.$wrapper.addClass('has-validation-error');
            var message = this.$wrapper.data('required-message') || 'Please select at least one item';
            this.$wrapper.find('.trait-validation-error').remove();
            var $error = $('<div>', {
                class: 'trait-validation-error',
                html: this.esIcon('warning') + ' ' + message
            });
            this.$wrapper.find('.condition-trait-header').after($error);
            $('html, body').animate({ scrollTop: this.$wrapper.offset().top - 100 }, 300);
            if (!this.$wrapper.find('.condition-trait-body').is(':visible')) {
                this.$wrapper.find('.condition-trait-body').slideDown(200);
                this.$wrapper.removeClass('collapsed');
            }
        },

        clearValidationError: function() {
            this.$wrapper.removeClass('has-validation-error');
            this.$wrapper.find('.trait-validation-error').remove();
        },

        getBlockMode: function(blockType) {
            var blockDef = this.config.blocks[blockType];
            return (blockDef && blockDef.mode) ? blockDef.mode : 'multi';
        },

        isBlockSingleMode: function(blockType) {
            return this.getBlockMode(blockType) === 'single';
        },

        getCurrentSingleSelection: function() {
            if ((this.config.mode || 'multi') !== 'single') return null;
            var $chip = this.$wrapper.find('.entity-chips .entity-chip').first();
            if ($chip.length) {
                var $block = $chip.closest('.target-block');
                return {
                    name: $chip.find('.chip-name').text() || $chip.data('id'),
                    entityType: $block.data('block-type') || 'item'
                };
            }
            return null;
        },

        /**
         * Check if entity type supports tree browsing
         */
        supportsTreeBrowsing: function(entityType) {
            return entityType === 'categories' || entityType === 'cms_categories';
        }
    };

})(jQuery);

/**
 * Entity Selector - Events Module
 * All event binding and handlers
 * @partial _events.js
 *
 * Contains event handlers for:
 * - Tab switching
 * - Block/group collapse toggle
 * - Dropdown open/close
 * - Search input handling
 * - Item selection/deselection
 * - Group add/remove
 * - Exclude row add/remove
 * - Method select changes
 * - Filter panel toggles
 * - Keyboard shortcuts (Ctrl+A, Ctrl+D, Esc, Enter)
 * - Load more pagination
 * - Sort controls
 * - View mode switching
 * - Tree view events
 * - Preview badge clicks
 * - Pattern tag interactions
 * - Combination picker events
 * - Group modifier events
 */

(function($) {
    'use strict';

    window._EntitySelectorMixins = window._EntitySelectorMixins || {};

    window._EntitySelectorMixins.events = {

        bindEvents: function() {
            var self = this;

            // Tab switching
            this.$wrapper.on('click', '.target-block-tab', function(e) {
                e.preventDefault();
                var blockType = $(this).data('blockType');
                self.switchToBlock(blockType);
            });

            // Tab badge click for preview popover (toggle)
            this.$wrapper.on('click', '.target-block-tab .tab-badge', function(e) {
                e.stopPropagation();
                e.preventDefault();

                var $tab = $(this).closest('.target-block-tab');
                var $badge = $(this);

                if ($badge.hasClass('popover-open')) {
                    self.hidePreviewPopover();
                } else {
                    self.showPreviewPopover($tab);
                }
            });

            // Condition count badge click for preview popover
            this.$wrapper.on('click', '.condition-match-count.clickable', function(e) {
                e.stopPropagation();
                e.preventDefault();

                var $badge = $(this);

                if ($badge.hasClass('popover-open')) {
                    self.hidePreviewPopover();
                } else {
                    self.showConditionPreviewPopover($badge);
                }
            });

            // Group count badge click for preview popover
            this.$wrapper.on('click', '.group-count-badge.clickable', function(e) {
                e.stopPropagation();
                e.preventDefault();

                var $badge = $(this);

                if ($badge.hasClass('popover-open')) {
                    self.hidePreviewPopover();
                } else {
                    self.showGroupPreviewPopover($badge);
                }
            });

            // Total count badge click for summary popover
            this.$wrapper.on('click', '.trait-total-count', function(e) {
                e.stopPropagation();
                e.preventDefault();

                var $badge = $(this);

                if ($badge.hasClass('popover-open')) {
                    self.hidePreviewPopover();
                } else {
                    self.showTotalPreviewPopover($badge);
                }
            });

            // Close popover when clicking outside
            $(document).on('click', function(e) {
                if (!$(e.target).closest('.target-preview-popover').length &&
                    !$(e.target).closest('.holiday-preview-popover').length &&
                    !$(e.target).closest('.tab-badge').length &&
                    !$(e.target).closest('.condition-match-count').length &&
                    !$(e.target).closest('.group-count-badge').length &&
                    !$(e.target).closest('.group-modifiers').length &&
                    !$(e.target).closest('.group-preview-badge').length &&
                    !$(e.target).closest('.toggle-count.clickable').length &&
                    !$(e.target).closest('.trait-total-count').length &&
                    !$(e.target).closest('.chip-preview-holidays').length) {
                    self.hidePreviewPopover();
                    // Also close holiday popover
                    $('.holiday-preview-popover').remove();
                }
            });

            // Block-level collapse toggle (click on header)
            this.$wrapper.on('click', '.condition-trait-header', function(e) {
                if ($(e.target).closest('.target-block-tabs').length ||
                    $(e.target).closest('.trait-header-actions').length ||
                    $(e.target).closest('.prestashop-switch').length ||
                    $(e.target).closest('.trait-total-count').length) {
                    return;
                }
                var $body = self.$wrapper.find('.condition-trait-body');
                $body.stop(true, true);
                if ($body.is(':visible')) {
                    $body.slideUp(200);
                    self.$wrapper.addClass('collapsed');
                } else {
                    $body.slideDown(200);
                    self.$wrapper.removeClass('collapsed');
                }
            });

            // Toggle blocks content (form-content layout)
            this.$wrapper.on('click', '.btn-toggle-blocks', function(e) {
                e.preventDefault();
                var $blocksContent = self.$wrapper.find('.entity-selector-blocks-content');
                var $icon = $(this).find('.es-icon');
                $blocksContent.stop(true, true);
                if ($blocksContent.is(':visible')) {
                    $blocksContent.slideUp(200);
                    self.$wrapper.addClass('blocks-collapsed');
                    $icon.text('expand_more');
                } else {
                    $blocksContent.slideDown(200);
                    self.$wrapper.removeClass('blocks-collapsed');
                    $icon.text('expand_less');
                }
            });

            // Custom block input changes — update tab badge when value changes
            this.$wrapper.on('input change', '.custom-block-content input, .custom-block-content textarea, .custom-block-content select', function() {
                self.updateTabBadges();
            });

            // Group-level collapse toggle (click on group header or toggle icon)
            this.$wrapper.on('click', '.group-header', function(e) {
                if ($(e.target).closest('.btn-remove-group, .group-name-input').length) {
                    return;
                }
                if (self.$wrapper.data('mode') === 'single') {
                    return;
                }
                var $group = $(this).closest('.selection-group');
                $group.toggleClass('collapsed');
            });

            // Toggle all groups (single button that switches between expand/collapse)
            console.log('[ES-DEBUG] Binding .btn-toggle-groups click on wrapper:', self.$wrapper.attr('id'), 'found buttons:', self.$wrapper.find('.btn-toggle-groups').length);
            this.$wrapper.on('click', '.btn-toggle-groups', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var $btn = $(this);
                var currentState = $btn.attr('data-state') || 'collapsed';
                var trans = self.config.trans || {};
                console.log('[ES-DEBUG] .btn-toggle-groups CLICKED! currentState:', currentState, 'btn parent:', $btn.parent().attr('class'), 'groups found:', self.$wrapper.find('.selection-group').length);

                if (currentState === 'collapsed') {
                    self.$wrapper.find('.selection-group').removeClass('collapsed');
                    $btn.attr('data-state', 'expanded');
                    $btn.attr('title', trans.collapse_all || 'Collapse all groups');
                    $btn.find('i').text('close_fullscreen');
                    console.log('[ES-DEBUG] Expanded all groups');
                } else {
                    self.$wrapper.find('.selection-group').addClass('collapsed');
                    $btn.attr('data-state', 'collapsed');
                    $btn.attr('title', trans.expand_all || 'Expand all groups');
                    $btn.find('i').text('open_in_full');
                    console.log('[ES-DEBUG] Collapsed all groups');
                }
            });

            // Show all toggle change (legacy checkbox)
            this.$wrapper.on('change', '.trait-show-all-toggle .show-all-checkbox', function(e) {
                e.stopPropagation();
                var isChecked = $(this).prop('checked');
                if (isChecked) {
                    self.clearAllConditions();
                }
            });

            // Target switch change (PrestaShop native switch)
            this.$wrapper.on('change', '.target-switch-toggle', function(e) {
                e.stopPropagation();
                var value = $(this).val();
                if (value === '1') {
                    self.clearAllConditions();
                    self.$wrapper.find('.condition-trait-body').slideUp(200);
                    self.$wrapper.addClass('collapsed');
                } else {
                    self.$wrapper.find('.condition-trait-body').slideDown(200);
                    self.$wrapper.removeClass('collapsed');
                }
            });

            // Add group
            this.$wrapper.on('click', '.btn-add-group', function(e) {
                e.preventDefault();
                var $block = $(this).closest('.target-block');
                var blockType = $block.data('blockType');
                self.addGroup($block, blockType);
            });

            // Remove group
            this.$wrapper.on('click', '.btn-remove-group', function(e) {
                e.preventDefault();
                var $group = $(this).closest('.selection-group');
                var $block = $(this).closest('.target-block');
                self.removeGroup($group, $block);
            });

            // Group name input - stop propagation to prevent collapse
            this.$wrapper.on('click focus', '.group-name-input', function(e) {
                e.stopPropagation();
            });

            // Group name change
            this.$wrapper.on('change blur', '.group-name-input', function() {
                var $input = $(this);
                var $group = $input.closest('.selection-group');
                var name = $.trim($input.val());
                $group.attr('data-group-name', name);
                self.serializeAllBlocks();
            });

            // Add exceptions (first exclude row)
            this.$wrapper.on('click', '.btn-add-exclude', function(e) {
                e.preventDefault();
                var $group = $(this).closest('.selection-group');
                var $block = $(this).closest('.target-block');
                self.addFirstExcludeRow($group, $block);
            });

            // Add another exclude row
            this.$wrapper.on('click', '.btn-add-another-exclude', function(e) {
                e.preventDefault();
                var $group = $(this).closest('.selection-group');
                var $block = $(this).closest('.target-block');
                self.addExcludeRow($group, $block);
            });

            // Remove individual exclude row
            this.$wrapper.on('click', '.btn-remove-exclude-row', function(e) {
                e.preventDefault();
                var $excludeRow = $(this).closest('.exclude-row');
                var $group = $(this).closest('.selection-group');
                var $block = $(this).closest('.target-block');
                self.removeExcludeRow($excludeRow, $group, $block);
            });

            // Include method change
            this.$wrapper.on('change', '.include-method-select', function() {
                self.hideDropdown();

                var $group = $(this).closest('.selection-group');
                var $block = $(this).closest('.target-block');
                var $row = $group.find('.group-include');
                var blockType = $block.data('blockType');
                var blockDef = self.config.blocks[blockType] || {};
                var methods = blockDef.selection_methods || {};

                var $option = $(this).find('option:selected');
                var valueType = $option.data('valueType') || 'none';
                var searchEntity = $option.data('searchEntity') || '';
                var methodOptions = $option.data('options') || null;

                var $oldPicker = $group.find('.include-picker');
                var newPickerHtml = self.buildValuePickerHtml('include', valueType, searchEntity, methods);
                $oldPicker.replaceWith(newPickerHtml);

                if (valueType === 'select' && methodOptions) {
                    var $newPicker = $group.find('.include-picker');
                    var $select = $newPicker.find('.select-value-input');
                    $select.empty();
                    $.each(methodOptions, function(key, label) {
                        $select.append('<option value="' + self.escapeAttr(key) + '">' + self.escapeHtml(label) + '</option>');
                    });
                }

                if (valueType === 'multi_select_tiles' && methodOptions) {
                    var $newPicker = $group.find('.include-picker');
                    var isExclusive = $option.data('exclusive') === true;
                    self.populateTiles($newPicker, methodOptions, isExclusive);
                }

                if (valueType === 'multi_numeric_range') {
                    var $newPicker = $group.find('.include-picker');
                    var step = $option.data('step');
                    var min = $option.data('min');
                    self.applyRangeInputConstraints($newPicker, step, min);
                }

                if (valueType === 'combination_attributes') {
                    var $newPicker = $group.find('.include-picker');
                    self.loadCombinationAttributeGroups($newPicker);
                }

                var selectedMethod = $(this).val();
                self.updateMethodInfoPlaceholder($group.find('.method-selector-wrapper'), selectedMethod, blockType);

                self.updateBlockStatus($block);
                self.serializeAllBlocks($row);
            });

            // Exclude method change (within an exclude row)
            this.$wrapper.on('change', '.exclude-method-select', function() {
                self.hideDropdown();

                var $excludeRow = $(this).closest('.exclude-row');
                var $group = $(this).closest('.selection-group');
                var $block = $(this).closest('.target-block');
                var blockType = $block.data('blockType');
                var blockDef = self.config.blocks[blockType] || {};
                var methods = blockDef.selection_methods || {};

                var $option = $(this).find('option:selected');
                var valueType = $option.data('valueType') || 'entity_search';
                var searchEntity = $option.data('searchEntity') || blockType;
                var methodOptions = $option.data('options') || null;

                var $oldPicker = $excludeRow.find('.exclude-picker');
                var newPickerHtml = self.buildValuePickerHtml('exclude', valueType, searchEntity, methods);
                $oldPicker.replaceWith(newPickerHtml);

                if (valueType === 'select' && methodOptions) {
                    var $newPicker = $excludeRow.find('.exclude-picker');
                    var $select = $newPicker.find('.select-value-input');
                    $select.empty();
                    $.each(methodOptions, function(key, label) {
                        $select.append('<option value="' + self.escapeAttr(key) + '">' + self.escapeHtml(label) + '</option>');
                    });
                }

                if (valueType === 'multi_select_tiles' && methodOptions) {
                    var $newPicker = $excludeRow.find('.exclude-picker');
                    var isExclusive = $option.data('exclusive') === true;
                    self.populateTiles($newPicker, methodOptions, isExclusive);
                }

                if (valueType === 'multi_numeric_range') {
                    var $newPicker = $excludeRow.find('.exclude-picker');
                    var step = $option.data('step');
                    var min = $option.data('min');
                    self.applyRangeInputConstraints($newPicker, step, min);
                }

                if (valueType === 'combination_attributes') {
                    var $newPicker = $excludeRow.find('.exclude-picker');
                    self.loadCombinationAttributeGroups($newPicker);
                }

                var selectedMethod = $(this).val();
                self.updateMethodInfoPlaceholder($excludeRow.find('.exclude-header-row'), selectedMethod, blockType);

                self.serializeAllBlocks($excludeRow);
            });

            // Handle pattern input Enter key - adds pattern as tag
            this.$wrapper.on('keydown', '.pattern-input', function(e) {
                if (e.keyCode === 13) {
                    e.preventDefault();
                    var $btn = $(this).closest('.draft-tag').find('.btn-add-pattern');
                    $btn.click();
                }
            });

            // Handle add pattern button click (in draft tag)
            this.$wrapper.on('click', '.draft-tag .btn-add-pattern', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var $draftTag = $(this).closest('.draft-tag');
                var $picker = $draftTag.closest('.value-picker');
                var $row = $draftTag.closest('.group-include, .exclude-row');
                var $input = $draftTag.find('.pattern-input');
                var pattern = $.trim($input.val());

                if (pattern) {
                    var caseSensitive = $draftTag.attr('data-case-sensitive') === '1';
                    self.addPatternTag($picker, pattern, caseSensitive);

                    $input.val('').focus();
                    $draftTag.find('.pattern-match-count').removeClass('count-found count-zero').hide();
                    $draftTag.find('.pattern-match-count .count-value').text('');

                    self.serializeAllBlocks($row);
                }
            });

            // Handle pattern input live typing - update match count in draft tag
            this.$wrapper.on('input', '.pattern-input', function() {
                var $input = $(this);
                var $draftTag = $input.closest('.draft-tag');
                if (!$draftTag.length) return;

                var pattern = $.trim($input.val());

                if ($input.data('countTimeout')) {
                    clearTimeout($input.data('countTimeout'));
                }

                var $matchCount = $draftTag.find('.pattern-match-count');

                if (!pattern) {
                    $matchCount.removeClass('count-found count-zero').hide();
                    $matchCount.find('.count-value').text('');
                    var $group = $draftTag.closest('.selection-group');
                    if ($group.length) {
                        self.updateGroupTotalCount($group);
                    }
                    return;
                }

                var timeout = setTimeout(function() {
                    var caseSensitive = $draftTag.attr('data-case-sensitive') === '1';
                    self.updateDraftTagCount($draftTag, pattern, caseSensitive);
                }, 300);
                $input.data('countTimeout', timeout);
            });

            // Handle pattern tag remove
            this.$wrapper.on('click', '.pattern-tag .btn-remove-pattern', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var $row = $(this).closest('.group-include, .exclude-row');
                $(this).closest('.pattern-tag').remove();
                self.serializeAllBlocks($row);
            });

            // Handle pattern tag case-sensitivity toggle
            this.$wrapper.on('click', '.pattern-tag .btn-toggle-case', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var $tag = $(this).closest('.pattern-tag');
                var $btn = $(this);
                var trans = self.config.trans || {};
                var isDraftTag = $tag.hasClass('draft-tag');

                var isCaseSensitive = $tag.data('caseSensitive') === 1 || $tag.data('caseSensitive') === '1' || $tag.attr('data-case-sensitive') === '1';
                var newCaseSensitive = !isCaseSensitive;

                $tag.data('caseSensitive', newCaseSensitive ? 1 : 0);
                $tag.attr('data-case-sensitive', newCaseSensitive ? '1' : '0');
                $tag.toggleClass('case-sensitive', newCaseSensitive);

                $btn.find('.case-icon').text(newCaseSensitive ? 'Aa' : 'aa');
                var caseTitle = newCaseSensitive
                    ? (trans.case_sensitive || 'Case sensitive - click to toggle')
                    : (trans.case_insensitive || 'Case insensitive - click to toggle');
                $btn.attr('title', caseTitle);

                if (isDraftTag) {
                    var pattern = $.trim($tag.find('.pattern-input').val());
                    if (pattern) {
                        self.updateDraftTagCount($tag, pattern, newCaseSensitive);
                    }
                } else {
                    var $row = $tag.closest('.group-include, .exclude-row');
                    self.serializeAllBlocks($row);
                }
            });

            // Handle pattern match count click - show preview modal
            this.$wrapper.on('click', '.pattern-match-count', function(e) {
                e.preventDefault();
                e.stopPropagation();

                var $matchCount = $(this);
                var count = $matchCount.data('count');
                var pattern = $matchCount.data('pattern');
                var entityType = $matchCount.data('entityType');
                var caseSensitive = $matchCount.data('caseSensitive');

                if (!count || count <= 0 || !pattern) {
                    return;
                }

                self.showPatternPreviewModal(pattern, entityType, caseSensitive, count);
            });

            // Handle pattern tag edit (click on tag text)
            this.$wrapper.on('click', '.pattern-tag .pattern-tag-text', function(e) {
                e.preventDefault();
                var $tag = $(this).closest('.pattern-tag');
                if ($tag.hasClass('editing')) return;

                var currentPattern = $tag.data('pattern');

                var $editInput = $('<input type="text" class="pattern-tag-edit">').val(currentPattern);
                var $saveBtn = $('<button type="button" class="btn-pattern-save" title="Save">' + this.esIcon('check') + '</button>');
                var $cancelBtn = $('<button type="button" class="btn-pattern-cancel" title="Cancel">' + this.esIcon('close') + '</button>');
                var $editActions = $('<span class="pattern-edit-actions"></span>').append($saveBtn, $cancelBtn);

                $tag.addClass('editing').find('.pattern-tag-text').hide();
                $tag.find('.btn-remove-pattern').hide();
                $tag.prepend($editActions).prepend($editInput);
                $editInput.focus().select();

                $editInput.on('keydown', function(ev) {
                    if (ev.keyCode === 13) {
                        ev.preventDefault();
                        $saveBtn.click();
                    } else if (ev.keyCode === 27) {
                        ev.preventDefault();
                        $cancelBtn.click();
                    }
                });
            });

            // Pattern edit - Save button
            this.$wrapper.on('click', '.pattern-tag .btn-pattern-save', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var $tag = $(this).closest('.pattern-tag');
                var $editInput = $tag.find('.pattern-tag-edit');
                var currentPattern = $tag.data('pattern');
                var newPattern = $.trim($editInput.val());

                if (newPattern && newPattern !== currentPattern) {
                    $tag.data('pattern', newPattern);
                    $tag.find('.pattern-tag-text').text(newPattern);
                }
                $editInput.remove();
                $tag.find('.pattern-edit-actions').remove();
                $tag.removeClass('editing').find('.pattern-tag-text, .btn-remove-pattern').show();
                var $row = $tag.closest('.group-include, .exclude-row');
                self.serializeAllBlocks($row);
            });

            // Pattern edit - Cancel button
            this.$wrapper.on('click', '.pattern-tag .btn-pattern-cancel', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var $tag = $(this).closest('.pattern-tag');
                $tag.find('.pattern-tag-edit').remove();
                $tag.find('.pattern-edit-actions').remove();
                $tag.removeClass('editing').find('.pattern-tag-text, .btn-remove-pattern').show();
            });

            // Handle numeric range input changes
            this.$wrapper.on('change', '.range-min-input, .range-max-input', function() {
                var $row = $(this).closest('.group-include, .exclude-row');
                self.serializeAllBlocks($row);
            });

            // Handle date range input changes
            this.$wrapper.on('change', '.date-from-input, .date-to-input', function() {
                var $row = $(this).closest('.group-include, .exclude-row');
                self.serializeAllBlocks($row);
            });

            // Handle select value changes
            this.$wrapper.on('change', '.select-value-input', function() {
                var $row = $(this).closest('.group-include, .exclude-row');
                self.serializeAllBlocks($row);
            });

            // Handle multi-range add button click
            this.$wrapper.on('click', '.btn-add-range', function(e) {
                e.preventDefault();
                var $picker = $(this).closest('.value-picker');
                var $row = $(this).closest('.group-include, .exclude-row');
                var $container = $picker.find('.multi-range-container');
                var $chipsContainer = $container.find('.multi-range-chips');
                var $minInput = $container.find('.range-min-input');
                var $maxInput = $container.find('.range-max-input');

                var minVal = $minInput.val().trim();
                var maxVal = $maxInput.val().trim();

                if (minVal === '' && maxVal === '') {
                    return;
                }

                var step = parseFloat($minInput.attr('step')) || 0.01;
                var minAllowed = $minInput.attr('min');
                var hasMinConstraint = typeof minAllowed !== 'undefined' && minAllowed !== '';
                minAllowed = hasMinConstraint ? parseFloat(minAllowed) : null;

                var minNum = minVal !== '' ? parseFloat(minVal) : null;
                var maxNum = maxVal !== '' ? parseFloat(maxVal) : null;

                if (hasMinConstraint) {
                    if (minNum !== null && minNum < minAllowed) {
                        self.showRangeInputError($minInput, self.config.trans.min_value_error || 'Minimum value is ' + minAllowed);
                        return;
                    }
                    if (maxNum !== null && maxNum < minAllowed) {
                        self.showRangeInputError($maxInput, self.config.trans.min_value_error || 'Minimum value is ' + minAllowed);
                        return;
                    }
                }

                if (minNum !== null && maxNum !== null && minNum > maxNum) {
                    self.showRangeInputError($minInput, self.config.trans.min_greater_than_max || 'Min cannot be greater than max');
                    return;
                }

                var decimals = step < 1 ? String(step).split('.')[1].length : 0;
                if (minNum !== null) {
                    if (step >= 1) {
                        minNum = Math.round(minNum);
                    } else {
                        minNum = parseFloat(minNum.toFixed(decimals));
                    }
                    minVal = String(minNum);
                }
                if (maxNum !== null) {
                    if (step >= 1) {
                        maxNum = Math.round(maxNum);
                    } else {
                        maxNum = parseFloat(maxNum.toFixed(decimals));
                    }
                    maxVal = String(maxNum);
                }

                var chipText = '';
                if (minVal !== '' && maxVal !== '') {
                    chipText = minVal + ' - ' + maxVal;
                } else if (minVal !== '') {
                    chipText = '≥ ' + minVal;
                } else {
                    chipText = '≤ ' + maxVal;
                }

                var $chip = $('<span>', {
                    class: 'range-chip',
                    'data-min': minVal,
                    'data-max': maxVal
                });
                $chip.append($('<span>', { class: 'range-chip-text', text: chipText }));
                $chip.append($('<button>', {
                    type: 'button',
                    class: 'btn-remove-range',
                    html: self.esIcon('close')
                }));

                $chipsContainer.append($chip);

                $minInput.val('');
                $maxInput.val('');

                self.serializeAllBlocks($row);
            });

            // Handle multi-range chip removal
            this.$wrapper.on('click', '.btn-remove-range', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var $chip = $(this).closest('.range-chip');
                var $row = $chip.closest('.group-include, .exclude-row');
                $chip.remove();
                self.serializeAllBlocks($row);
            });

            // Handle Enter key in multi-range inputs
            this.$wrapper.on('keydown', '.multi-range-container .range-min-input, .multi-range-container .range-max-input', function(e) {
                if (e.keyCode === 13) {
                    e.preventDefault();
                    $(this).closest('.multi-range-container').find('.btn-add-range').click();
                }
            });

            // Handle multi-select tile clicks
            this.$wrapper.on('click', '.tile-option', function(e) {
                e.preventDefault();
                var $tile = $(this);
                var $container = $tile.closest('.multi-select-tiles');
                var $row = $tile.closest('.group-include, .exclude-row');
                var isExclusive = $container.attr('data-exclusive') === 'true';

                if (isExclusive) {
                    if ($tile.hasClass('selected')) {
                        $tile.removeClass('selected');
                    } else {
                        $container.find('.tile-option').removeClass('selected');
                        $tile.addClass('selected');
                    }
                } else {
                    $tile.toggleClass('selected');
                }

                self.serializeAllBlocks($row);
            });

            // Handle combination attribute value toggle
            this.$wrapper.on('click', '.comb-attr-value', function(e) {
                e.preventDefault();
                var $value = $(this);
                var $row = $value.closest('.group-include, .exclude-row');
                var $picker = $value.closest('.value-picker');

                $value.toggleClass('selected');
                self.updateCombinationData($picker);
                self.serializeAllBlocks($row);
            });

            // Handle combination mode toggle
            this.$wrapper.on('change', '.comb-mode-radio', function() {
                var $picker = $(this).closest('.value-picker');
                var $row = $(this).closest('.group-include, .exclude-row');
                self.updateCombinationData($picker);
                self.serializeAllBlocks($row);
            });

            // Handle combination select all
            this.$wrapper.on('click', '.comb-select-all', function(e) {
                e.preventDefault();
                var $group = $(this).closest('.comb-attr-group');
                var $picker = $(this).closest('.value-picker');
                var $row = $(this).closest('.group-include, .exclude-row');
                $group.find('.comb-attr-value:visible').addClass('selected');
                self.updateCombinationData($picker);
                self.serializeAllBlocks($row);
            });

            // Handle combination select none
            this.$wrapper.on('click', '.comb-select-none', function(e) {
                e.preventDefault();
                var $group = $(this).closest('.comb-attr-group');
                var $picker = $(this).closest('.value-picker');
                var $row = $(this).closest('.group-include, .exclude-row');
                $group.find('.comb-attr-value').removeClass('selected');
                self.updateCombinationData($picker);
                self.serializeAllBlocks($row);
            });

            // Handle combination attribute search/filter
            this.$wrapper.on('input', '.comb-attr-search', function() {
                var query = $(this).val().toLowerCase().trim();
                var $group = $(this).closest('.comb-attr-group');
                $group.find('.comb-attr-value').each(function() {
                    var name = $(this).data('name') || '';
                    if (!query || name.indexOf(query) !== -1) {
                        $(this).show();
                    } else {
                        $(this).hide();
                    }
                });
            });

            // Handle group-level modifier toggle
            this.$wrapper.on('click', '.btn-toggle-modifiers', function(e) {
                e.preventDefault();
                var $btn = $(this);
                var $modifiers = $btn.closest('.group-modifiers');
                var $content = $modifiers.find('.group-modifiers-content');
                $content.slideToggle(150, function() {
                    $modifiers.toggleClass('expanded', $content.is(':visible'));
                });
            });

            // Handle group-level modifier changes
            this.$wrapper.on('change input', '.group-modifier-limit', function() {
                var $group = $(this).closest('.selection-group');
                var $limitInput = $(this);
                var limit = parseInt($limitInput.val(), 10);
                var $badge = $group.find('.group-header .group-count-badge');
                var finalCount = $badge.data('finalCount') || 0;

                var $previewBadge = $group.find('.group-preview-badge .preview-count');
                if ($previewBadge.length && finalCount > 0) {
                    var displayCount = (limit > 0 && limit < finalCount) ? limit : finalCount;
                    $previewBadge.text(displayCount);
                }

                self.updateModifierButtonState($group);
                self.serializeAllBlocks();
                self.refreshGroupPreviewIfOpen($group);
            });

            // Sort dropdown change
            this.$wrapper.on('change', '.group-modifier-sort', function() {
                var $group = $(this).closest('.selection-group');
                self.serializeAllBlocks();
                self.refreshGroupPreviewIfOpen($group);
            });

            // Sort direction button click
            this.$wrapper.on('click', '.group-modifiers .btn-sort-dir', function(e) {
                e.preventDefault();
                var $btn = $(this);
                var $group = $btn.closest('.selection-group');
                var currentDir = $btn.data('dir') || 'DESC';
                var newDir = (currentDir === 'DESC') ? 'ASC' : 'DESC';

                $btn.data('dir', newDir);
                $btn.attr('data-dir', newDir);

                var $icon = $btn.find('i');
                $icon.replaceWith(this.esIcon('sort'));

                self.serializeAllBlocks();
                self.refreshGroupPreviewIfOpen($group);
            });

            // Group preview badge click
            this.$wrapper.on('click', '.group-preview-badge.clickable', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var $badge = $(this);
                var $group = $badge.closest('.selection-group');
                var $block = $badge.closest('.target-block');
                var blockType = $block.data('blockType');

                if ($badge.hasClass('popover-open')) {
                    self.hidePreviewPopover();
                    return;
                }

                self.showGroupPreviewPopover($badge, $group, blockType);
            });

            // Search input focus
            this.$wrapper.on('focus', '.entity-search-input', function() {
                var $picker = $(this).closest('.value-picker');
                var $group = $(this).closest('.selection-group');
                var $block = $(this).closest('.target-block');
                var blockType = $block.data('blockType');
                var groupIndex = parseInt($group.data('groupIndex'), 10);
                var section = $picker.hasClass('include-picker') ? 'include' : 'exclude';
                var searchEntity = $picker.attr('data-search-entity') || blockType;

                var excludeIndex = null;
                if (section === 'exclude') {
                    var $excludeRow = $(this).closest('.exclude-row');
                    if ($excludeRow.length) {
                        excludeIndex = parseInt($excludeRow.data('excludeIndex'), 10);
                    }
                }

                var entityChanged = self.activeGroup && self.activeGroup.searchEntity !== searchEntity;
                if (entityChanged) {
                    self.searchResults = [];
                    self.searchOffset = 0;
                    self.searchQuery = '';
                    self.viewMode = 'list';
                    self.resetFiltersWithoutSearch();
                    self.$dropdown.find('.dropdown-results').empty();
                    self.$dropdown.find('.filter-panel').removeClass('show');
                    self.$dropdown.find('.btn-toggle-filters').removeClass('active');
                }

                self.activeGroup = {
                    blockType: blockType,
                    groupIndex: groupIndex,
                    section: section,
                    excludeIndex: excludeIndex,
                    searchEntity: searchEntity
                };

                // Initialize pending selections from current chips
                var $chips = $picker.find('.entity-chips');
                self.pendingSelections = [];
                $chips.find('.entity-chip').each(function() {
                    self.pendingSelections.push({
                        id: $(this).data('id'),
                        name: $(this).find('.chip-name').text(),
                        data: $(this).data()
                    });
                });
                self.pendingPicker = $picker;
                self.pendingRow = section === 'include' ? $group.find('.group-include') : $group.find('.exclude-row[data-exclude-index="' + excludeIndex + '"]');

                self.searchOffset = 0;
                self.searchQuery = $(this).val().trim();

                self.updateFilterPanelForEntity(searchEntity);

                if (searchEntity === 'products') {
                    self.loadFilterableData();
                }

                self.positionDropdown($(this));

                // For tree view mode on categories, load category tree instead of search
                if (self.viewMode === 'tree' && (searchEntity === 'categories' || searchEntity === 'cms_categories')) {
                    self.loadCategoryTree();
                    return;
                }

                self.performSearch();
            });

            // Search input typing
            this.$wrapper.on('input', '.entity-search-input', function() {
                var query = $(this).val().trim();
                self.searchQuery = query;
                self.searchOffset = 0;

                if (self.viewMode === 'tree') {
                    self.filterCategoryTree(query);
                    return;
                }

                clearTimeout(self.searchTimeout);
                self.searchTimeout = setTimeout(function() {
                    self.performSearch();
                }, 300);
            });

            // History item click
            this.$dropdown.on('click', '.history-item', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var query = $(this).data('query');
                if (query && self.activeGroup) {
                    var $input = self.$wrapper.find('.entity-search-input:focus');
                    if (!$input.length) {
                        var $block = self.$wrapper.find('.target-block[data-block-type="' + self.activeGroup.blockType + '"]');
                        var $group = $block.find('.selection-group[data-group-index="' + self.activeGroup.groupIndex + '"]');
                        $input = $group.find('.entity-search-input').first();
                    }
                    $input.val(query);
                    self.searchQuery = query;
                    self.searchOffset = 0;
                    self.performSearch();
                }
            });

            // Delete history item
            this.$dropdown.on('click', '.history-item .btn-delete-history', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var $item = $(this).closest('.history-item');
                var query = $item.data('query');
                if (query && self.activeGroup) {
                    self.removeFromSearchHistory(self.activeGroup.searchEntity, query);
                    $item.fadeOut(150, function() {
                        $(this).remove();
                        if (!self.$dropdown.find('.history-item').length) {
                            self.performSearch();
                        }
                    });
                }
            });

            // Dropdown item click
            this.$dropdown.on('click', '.dropdown-item', function(e) {
                e.preventDefault();

                // Blur any focused input so Ctrl+A works for select all
                $(document.activeElement).filter('input, textarea').blur();

                var $item = $(this);
                var id = $item.data('id');
                var name = $item.data('name');
                var isSelected = $item.hasClass('selected');

                if (!self.activeGroup) return;

                var $block = self.$wrapper.find('.target-block[data-block-type="' + self.activeGroup.blockType + '"]');
                var $group = $block.find('.selection-group[data-group-index="' + self.activeGroup.groupIndex + '"]');
                var $picker;
                var $row;

                if (self.activeGroup.section === 'include') {
                    $picker = $group.find('.include-picker');
                    $row = $group.find('.group-include');
                } else {
                    var $excludeRow = $group.find('.exclude-row[data-exclude-index="' + self.activeGroup.excludeIndex + '"]');
                    $picker = $excludeRow.find('.exclude-picker');
                    $row = $excludeRow;
                }

                // Ensure pendingSelections is initialized
                if (!self.pendingSelections) self.pendingSelections = [];

                if (isSelected) {
                    // Remove from pending selections
                    self.pendingSelections = self.pendingSelections.filter(function(s) {
                        return parseInt(s.id, 10) !== parseInt(id, 10);
                    });
                    self.removeSelection($picker, id);
                    $item.toggleClass('selected');
                    self.serializeAllBlocks($row);
                } else {
                    // Validate selection before adding
                    var section = self.activeGroup.section;
                    var validation = self.validateSelection(id, name, section, $item.data());
                    if (!validation.valid) {
                        self.showValidationError(validation.error);
                        return;
                    }

                    var activeBlockType = self.activeGroup.blockType;
                    var currentSelection = self.getCurrentSingleSelection(activeBlockType);
                    if (currentSelection) {
                        var newEntityType = activeBlockType;
                        self.showReplaceConfirmation(currentSelection, { name: name, entityType: newEntityType }, function() {
                            // Add to pending selections
                            self.pendingSelections.push({ id: id, name: name, data: $item.data() });
                            self.addSelection($picker, id, name, $item.data());
                            $item.addClass('selected');
                            self.serializeAllBlocks($row);
                        });
                    } else {
                        // Add to pending selections
                        var exists = self.pendingSelections.some(function(s) {
                            return parseInt(s.id, 10) === parseInt(id, 10);
                        });
                        if (!exists) {
                            self.pendingSelections.push({ id: id, name: name, data: $item.data() });
                        }
                        self.addSelection($picker, id, name, $item.data());
                        $item.toggleClass('selected');
                        self.serializeAllBlocks($row);
                    }
                }
            });

            // Chip remove
            this.$wrapper.on('click', '.chip-remove', function(e) {
                e.stopPropagation();
                var $chip = $(this).closest('.entity-chip');
                var $picker = $(this).closest('.value-picker');
                var $row = $(this).closest('.group-include, .exclude-row');
                var id = $chip.data('id');

                console.log('[EntitySelector] Chip remove clicked, id:', id);

                // Also remove from pending selections if dropdown is open
                if (self.pendingSelections) {
                    self.pendingSelections = self.pendingSelections.filter(function(s) {
                        return parseInt(s.id, 10) !== parseInt(id, 10);
                    });
                }

                self.removeSelection($picker, id);
                console.log('[EntitySelector] Calling serializeAllBlocks after chip remove');
                self.serializeAllBlocks($row);

                if (self.$dropdown && self.$dropdown.hasClass('show')) {
                    self.$dropdown.find('.dropdown-item[data-id="' + id + '"]').removeClass('selected');
                }
            });

            // Country chip holiday preview
            this.$wrapper.on('click', '.chip-preview-holidays', function(e) {
                e.stopPropagation();
                e.preventDefault();

                var $btn = $(this);
                var $chip = $btn.closest('.entity-chip');
                var countryId = $chip.data('id');
                var countryName = $chip.find('.chip-name').text();
                var countryIso = $chip.data('iso') || '';

                self.showHolidayPreview(countryId, countryName, countryIso, $btn);
            });

            // Chips show more/less toggle
            this.$wrapper.on('click', '.chips-show-more-toggle', function(e) {
                e.stopPropagation();
                var $chips = $(this).closest('.entity-chips');

                if ($chips.hasClass('chips-expanded')) {
                    $chips.removeClass('chips-expanded').addClass('chips-collapsed');
                } else {
                    $chips.addClass('chips-expanded').removeClass('chips-collapsed');
                }

                self.updateChipsVisibility($chips);
            });

            // Select All
            this.$dropdown.on('click', '.btn-select-all', function(e) {
                e.preventDefault();
                if (!self.activeGroup) return;

                var section = self.activeGroup.section;
                var skippedCount = 0;

                // Handle tree view - use pending selections
                if (self.viewMode === 'tree') {
                    if (!self.pendingSelections) self.pendingSelections = [];

                    // Select all visible (not filtered-out) tree items
                    var $visibleTreeItems = self.$dropdown.find('.tree-item:not(.filtered-out)');
                    $visibleTreeItems.each(function() {
                        var $item = $(this);
                        var id = parseInt($item.data('id'), 10);
                        var name = $item.data('name');

                        if (!$item.hasClass('selected')) {
                            // Validate before adding
                            var validation = self.validateSelection(id, name, section, $item.data());
                            if (!validation.valid) {
                                skippedCount++;
                                return; // Skip this item
                            }

                            $item.addClass('selected');
                            var exists = self.pendingSelections.some(function(s) {
                                return parseInt(s.id, 10) === id;
                            });
                            if (!exists) {
                                self.pendingSelections.push({ id: id, name: name, data: $item.data() });
                            }
                        }
                    });

                    // Show warning if items were skipped
                    if (skippedCount > 0) {
                        var trans = self.config.trans || {};
                        var msg = (trans.items_skipped_conflicts || '{count} item(s) were skipped due to conflicts.').replace('{count}', skippedCount);
                        self.showValidationError(msg);
                    }

                    // Update count display
                    var selectedCount = self.$dropdown.find('.tree-item.selected').length;
                    var totalCount = self.$dropdown.find('.tree-item').length;
                    var entityType = self.$dropdown.find('.category-tree').data('entity-type') || 'categories';
                    var categoryLabel = entityType === 'cms_categories' ? 'CMS categories' : 'categories';
                    self.$dropdown.find('.results-count').text(totalCount + ' ' + categoryLabel + ' (' + selectedCount + ' selected)');
                    return;
                }

                // Handle list view
                var $block = self.$wrapper.find('.target-block[data-block-type="' + self.activeGroup.blockType + '"]');
                var $group = $block.find('.selection-group[data-group-index="' + self.activeGroup.groupIndex + '"]');
                var $picker;
                var $row;

                if (self.activeGroup.section === 'include') {
                    $picker = $group.find('.include-picker');
                    $row = $group.find('.group-include');
                } else {
                    var $excludeRow = $group.find('.exclude-row[data-exclude-index="' + self.activeGroup.excludeIndex + '"]');
                    $picker = $excludeRow.find('.exclude-picker');
                    $row = $excludeRow;
                }

                // Ensure pendingSelections is initialized
                if (!self.pendingSelections) self.pendingSelections = [];

                var $visibleItems = self.$dropdown.find('.dropdown-item:visible');
                $visibleItems.each(function() {
                    if (!$(this).hasClass('selected')) {
                        var id = $(this).data('id');
                        var name = $(this).data('name');

                        // Validate before adding
                        var validation = self.validateSelection(id, name, section, $(this).data());
                        if (!validation.valid) {
                            skippedCount++;
                            return; // Skip this item
                        }

                        // Add to pending selections for Save button
                        var exists = self.pendingSelections.some(function(s) {
                            return parseInt(s.id, 10) === parseInt(id, 10);
                        });
                        if (!exists) {
                            self.pendingSelections.push({
                                id: id,
                                name: name,
                                data: $(this).data()
                            });
                        }

                        self.addSelectionNoUpdate($picker, id, name, $(this).data());
                        $(this).addClass('selected');
                    }
                });

                // Show warning if items were skipped
                if (skippedCount > 0) {
                    var trans = self.config.trans || {};
                    var msg = (trans.items_skipped_conflicts || '{count} item(s) were skipped due to conflicts.').replace('{count}', skippedCount);
                    self.showValidationError(msg);
                }

                var $chips = $picker.find('.entity-chips');
                self.updateChipsVisibility($chips);

                self.serializeAllBlocks($row);
            });

            // Clear selection
            this.$dropdown.on('click', '.btn-clear-selection', function(e) {
                e.preventDefault();
                if (!self.activeGroup) return;

                // Handle tree view - clear pending selections
                if (self.viewMode === 'tree') {
                    self.pendingSelections = [];
                    self.$dropdown.find('.tree-item').removeClass('selected');

                    // Update count display
                    var totalCount = self.$dropdown.find('.tree-item').length;
                    var entityType = self.$dropdown.find('.category-tree').data('entity-type') || 'categories';
                    var categoryLabel = entityType === 'cms_categories' ? 'CMS categories' : 'categories';
                    self.$dropdown.find('.results-count').text(totalCount + ' ' + categoryLabel);
                    return;
                }

                // Handle list view
                var $block = self.$wrapper.find('.target-block[data-block-type="' + self.activeGroup.blockType + '"]');
                var $group = $block.find('.selection-group[data-group-index="' + self.activeGroup.groupIndex + '"]');
                var $picker;
                var $row;

                if (self.activeGroup.section === 'include') {
                    $picker = $group.find('.include-picker');
                    $row = $group.find('.group-include');
                } else {
                    var $excludeRow = $group.find('.exclude-row[data-exclude-index="' + self.activeGroup.excludeIndex + '"]');
                    $picker = $excludeRow.find('.exclude-picker');
                    $row = $excludeRow;
                }

                // Clear pending selections for list view too
                self.pendingSelections = [];

                var $chips = $picker.find('.entity-chips');
                $chips.empty().removeClass('chips-expanded chips-collapsed');
                self.$dropdown.find('.dropdown-item').removeClass('selected');
                self.serializeAllBlocks($row);
            });

            // Save - commit pending selections to chips
            this.$dropdown.on('click', '.btn-save', function(e) {
                e.preventDefault();

                if (self.pendingPicker && self.pendingSelections) {
                    var $chips = self.pendingPicker.find('.entity-chips');

                    // Clear existing chips
                    $chips.empty();

                    // Add chips for all pending selections
                    self.pendingSelections.forEach(function(sel) {
                        self.addSelectionNoUpdate(self.pendingPicker, sel.id, sel.name, sel.data);
                    });

                    self.updateChipsVisibility($chips);

                    // Serialize to hidden input
                    if (self.pendingRow) {
                        self.serializeAllBlocks(self.pendingRow);
                    }
                }

                self.pendingSelections = null;
                self.pendingPicker = null;
                self.pendingRow = null;
                self.hideDropdown();
            });

            // Cancel - discard pending selections (no changes to chips)
            this.$dropdown.on('click', '.btn-cancel', function(e) {
                e.preventDefault();

                // Just discard pending - chips remain unchanged
                self.pendingSelections = null;
                self.pendingPicker = null;
                self.pendingRow = null;
                self.hideDropdown();
            });

            // Load more
            this.$dropdown.on('click', '.btn-load-more', function(e) {
                e.preventDefault();
                if (self.isLoading) return;

                var loadCount = parseInt(self.$dropdown.find('.load-more-select').val(), 10) || 20;
                self.searchOffset = self.searchResults.length;
                self.loadMoreCount = loadCount;
                self.performSearch(true);
            });

            // Sort field change
            this.$dropdown.on('change', '.sort-field-select', function() {
                self.currentSort.field = $(this).val();
                self.refreshSearch();
            });

            // Sort direction toggle
            this.$dropdown.on('click', '.btn-sort-dir', function(e) {
                e.preventDefault();
                var $btn = $(this);
                var currentDir = $btn.data('dir');
                var newDir = currentDir === 'ASC' ? 'DESC' : 'ASC';
                $btn.data('dir', newDir);
                $btn.find('i').replaceWith(this.esIcon('sort_by_alpha'));
                self.currentSort.dir = newDir;
                self.refreshSearch();
            });

            // Tree view: Toggle expand/collapse
            this.$dropdown.on('click', '.category-tree .tree-toggle', function(e) {
                e.stopPropagation();
                var $item = $(this).closest('.tree-item');
                var $allItems = self.$dropdown.find('.tree-item');

                $item.toggleClass('collapsed');
                var isCollapsed = $item.hasClass('collapsed');

                $(this).find('i').text(isCollapsed ? 'arrow_right' : 'arrow_drop_down');

                var descendants = self.findTreeDescendants($item, $allItems);
                for (var i = 0; i < descendants.length; i++) {
                    $(descendants[i]).toggle(!isCollapsed);
                }
            });

            // Tree view: Item click (select/deselect) - PENDING mode
            this.$dropdown.on('click', '.category-tree .tree-item', function(e) {
                if ($(e.target).closest('.tree-toggle, .btn-select-children, .tree-count').length) {
                    return;
                }

                // Blur any focused input so Ctrl+A works for select all
                $(document.activeElement).filter('input, textarea').blur();

                var $item = $(this);
                var id = parseInt($item.data('id'), 10);
                var name = $item.data('name');
                var isSelected = $item.hasClass('selected');

                if (!self.activeGroup) {
                    return;
                }
                if (!self.pendingSelections) self.pendingSelections = [];

                var $allItems = self.$dropdown.find('.tree-item');

                var updateCount = function() {
                    var selectedCount = self.$dropdown.find('.tree-item.selected').length;
                    var totalCount = self.$dropdown.find('.tree-item').length;
                    var entityType = self.$dropdown.find('.category-tree').data('entity-type') || 'categories';
                    var categoryLabel = entityType === 'cms_categories' ? 'CMS categories' : 'categories';
                    self.$dropdown.find('.results-count').text(totalCount + ' ' + categoryLabel + (selectedCount > 0 ? ' (' + selectedCount + ' selected)' : ''));
                    self.updateSelectChildrenButtons($allItems);
                };

                if (isSelected) {
                    // Remove from pending
                    self.pendingSelections = self.pendingSelections.filter(function(s) {
                        return parseInt(s.id, 10) !== id;
                    });
                    $item.removeClass('selected');
                } else {
                    // Validate selection before adding to pending
                    var section = self.activeGroup.section;
                    var validation = self.validateSelection(id, name, section, $item.data());
                    if (!validation.valid) {
                        self.showValidationError(validation.error);
                        return;
                    }

                    // Add to pending
                    var exists = self.pendingSelections.some(function(s) {
                        return parseInt(s.id, 10) === id;
                    });
                    if (!exists) {
                        self.pendingSelections.push({
                            id: id,
                            name: name,
                            data: $item.data()
                        });
                    }
                    $item.addClass('selected');
                }

                updateCount();
            });

            // Tree view: Product/page count click - show preview
            this.$dropdown.on('click', '.category-tree .tree-count.clickable', function(e) {
                e.preventDefault();
                e.stopPropagation();
                var $count = $(this);
                var categoryId = $count.data('category-id');
                var $item = $count.closest('.tree-item');
                var categoryName = $item.data('name');
                var entityType = self.$dropdown.find('.category-tree').data('entity-type') || 'categories';

                if ($count.hasClass('popover-open')) {
                    self.hidePreviewPopover();
                } else {
                    self.showCategoryItemsPreview($count, categoryId, categoryName, entityType);
                }
            });

            // Tree view: Select/Deselect with children button (toggle)
            this.$dropdown.on('click', '.category-tree .btn-select-children', function(e) {
                e.stopPropagation();
                var $btn = $(this);
                var $item = $btn.closest('.tree-item');
                var $allItems = self.$dropdown.find('.tree-item');

                if (!self.activeGroup) return;

                var $block = self.$wrapper.find('.target-block[data-block-type="' + self.activeGroup.blockType + '"]');
                var $group = $block.find('.selection-group[data-group-index="' + self.activeGroup.groupIndex + '"]');
                var $picker;
                var $row;

                if (self.activeGroup.section === 'include') {
                    $picker = $group.find('.include-picker');
                    $row = $group.find('.group-include');
                } else {
                    var $excludeRow = $group.find('.exclude-row[data-exclude-index="' + self.activeGroup.excludeIndex + '"]');
                    $picker = $excludeRow.find('.exclude-picker');
                    $row = $excludeRow;
                }

                var descendants = self.findTreeDescendants($item, $allItems);

                var allSelected = $item.hasClass('selected');
                for (var i = 0; i < descendants.length && allSelected; i++) {
                    if (!$(descendants[i]).hasClass('selected')) {
                        allSelected = false;
                    }
                }

                var trans = self.config.trans || {};

                if (allSelected) {
                    self.removeSelection($picker, $item.data('id'));
                    $item.removeClass('selected');

                    for (var j = 0; j < descendants.length; j++) {
                        var $child = $(descendants[j]);
                        self.removeSelection($picker, $child.data('id'));
                        $child.removeClass('selected');
                    }

                    $btn.find('i').replaceWith(self.esIcon('add_box'));
                    $btn.attr('title', trans.select_with_children || 'Select with all children');
                } else {
                    var section = self.activeGroup.section;
                    var skippedChildren = 0;

                    if (!$item.hasClass('selected')) {
                        // Validate parent first
                        var parentValidation = self.validateSelection($item.data('id'), $item.data('name'), section, $item.data());
                        if (parentValidation.valid) {
                            self.addSelectionNoUpdate($picker, $item.data('id'), $item.data('name'), $item.data());
                            $item.addClass('selected');
                        } else {
                            self.showValidationError(parentValidation.error);
                            return; // Can't select parent, abort entire operation
                        }
                    }

                    for (var k = 0; k < descendants.length; k++) {
                        var $descendant = $(descendants[k]);
                        if (!$descendant.hasClass('selected')) {
                            // Validate each child
                            var childValidation = self.validateSelection($descendant.data('id'), $descendant.data('name'), section, $descendant.data());
                            if (childValidation.valid) {
                                self.addSelectionNoUpdate($picker, $descendant.data('id'), $descendant.data('name'), $descendant.data());
                                $descendant.addClass('selected');
                            } else {
                                skippedChildren++;
                            }
                        }
                    }

                    // Show warning if children were skipped
                    if (skippedChildren > 0) {
                        var skipMsg = (trans.children_skipped_conflicts || '{count} child item(s) were skipped due to conflicts.').replace('{count}', skippedChildren);
                        self.showValidationError(skipMsg);
                    }

                    $btn.find('i').replaceWith(self.esIcon('indeterminate_check_box'));
                    $btn.attr('title', trans.deselect_with_children || 'Deselect with all children');
                }

                var $chips = $picker.find('.entity-chips');
                self.updateChipsVisibility($chips);

                self.serializeAllBlocks($row);
                self.updateSelectChildrenButtons($allItems);

                var selectedCount = self.$dropdown.find('.tree-item.selected').length;
                var totalCount = self.$dropdown.find('.tree-item').length;
                var entityType = self.$dropdown.find('.category-tree').data('entity-type') || 'categories';
                var categoryLabel = entityType === 'cms_categories' ? 'CMS categories' : 'categories';
                self.$dropdown.find('.results-count').text(totalCount + ' ' + categoryLabel + (selectedCount > 0 ? ' (' + selectedCount + ' selected)' : ''));
            });

            // Tree view: Expand all
            this.$dropdown.on('click', '.category-tree .btn-expand-all', function(e) {
                e.preventDefault();
                self.$dropdown.find('.tree-item').removeClass('collapsed').show();
                self.$dropdown.find('.tree-toggle i').replaceWith(this.esIcon('arrow_drop_down'));
            });

            // Tree view: Collapse all
            this.$dropdown.on('click', '.category-tree .btn-collapse-all', function(e) {
                e.preventDefault();
                var minLevel = Infinity;
                self.$dropdown.find('.tree-item').each(function() {
                    var level = parseInt($(this).data('level'), 10);
                    if (level < minLevel) minLevel = level;
                });

                self.$dropdown.find('.tree-item').each(function() {
                    var $item = $(this);
                    var level = parseInt($item.data('level'), 10);
                    var hasChildren = $item.hasClass('has-children');

                    if (level === minLevel) {
                        if (hasChildren) {
                            $item.addClass('collapsed');
                            $item.find('.tree-toggle i').replaceWith(self.esIcon('arrow_right'));
                        }
                        $item.show();
                    } else {
                        $item.hide();
                    }
                });
            });

            // Refine search input
            this.$dropdown.on('keyup', '.refine-input', function() {
                var query = $(this).val().trim();
                self.refineQuery = query;
                self.$dropdown.find('.btn-clear-refine').toggle(query.length > 0);

                clearTimeout(self.refineTimeout);
                self.refineTimeout = setTimeout(function() {
                    // For tree view, filter client-side instead of server refresh
                    if (self.viewMode === 'tree') {
                        self.filterCategoryTree(query);
                        return;
                    }
                    self.refreshSearch();
                }, 300);
            });

            // Clear refine search
            this.$dropdown.on('click', '.btn-clear-refine', function(e) {
                e.preventDefault();
                self.refineQuery = '';
                self.$dropdown.find('.refine-input').val('');
                $(this).hide();
                // For tree view, filter client-side instead of server refresh
                if (self.viewMode === 'tree') {
                    self.filterCategoryTree('');
                    return;
                }
                self.refreshSearch();
            });

            // Toggle refine negate (NOT contains)
            this.$dropdown.on('click', '.btn-refine-negate', function(e) {
                e.preventDefault();
                self.refineNegate = !self.refineNegate;
                $(this).toggleClass('active', self.refineNegate);

                var trans = self.config.trans || {};
                var placeholder = self.refineNegate
                    ? (trans.refine_exclude || 'Exclude...')
                    : (trans.refine_short || 'Refine...');
                self.$dropdown.find('.refine-input').attr('placeholder', placeholder);

                if (self.refineQuery) {
                    self.refreshSearch();
                }
            });

            // Toggle filter panel
            this.$dropdown.on('click', '.btn-toggle-filters', function(e) {
                e.preventDefault();
                var $panel = self.$dropdown.find('.filter-panel');
                $panel.toggleClass('show');
                $(this).toggleClass('active', $panel.hasClass('show'));

                if ($panel.hasClass('show') && self.activeGroup) {
                    self.updateFilterPanelForEntity(self.activeGroup.searchEntity);
                }
            });

            // Show search history
            this.$dropdown.on('click', '.btn-show-history', function(e) {
                e.preventDefault();
                $(this).toggleClass('active');
                if ($(this).hasClass('active') && self.activeGroup) {
                    self.showSearchHistory(self.activeGroup.searchEntity);
                } else {
                    self.performSearch();
                }
            });

            // Quick filter checkboxes
            this.$dropdown.on('change', '.filter-in-stock', function() {
                self.filters.inStock = $(this).is(':checked');
                self.refreshSearch();
            });

            this.$dropdown.on('change', '.filter-discounted', function() {
                self.filters.discounted = $(this).is(':checked');
                self.refreshSearch();
            });

            // Price range filter
            this.$dropdown.on('change', '.filter-price-min, .filter-price-max', function() {
                var $panel = self.$dropdown.find('.filter-panel');
                self.filters.priceMin = $panel.find('.filter-price-min').val() || null;
                self.filters.priceMax = $panel.find('.filter-price-max').val() || null;
                self.refreshSearch();
            });

            // Entity-specific filters: Product count range
            this.$dropdown.on('change', '.filter-product-count-min, .filter-product-count-max', function() {
                var $row = $(this).closest('.filter-row');
                self.filters.productCountMin = $row.find('.filter-product-count-min').val() || null;
                self.filters.productCountMax = $row.find('.filter-product-count-max').val() || null;
                self.refreshSearch();
            });

            // Entity-specific filters: Sales range
            this.$dropdown.on('change', '.filter-sales-min, .filter-sales-max', function() {
                var $row = $(this).closest('.filter-row');
                self.filters.salesMin = $row.find('.filter-sales-min').val() || null;
                self.filters.salesMax = $row.find('.filter-sales-max').val() || null;
                self.refreshSearch();
            });

            // Entity-specific filters: Turnover/revenue range
            this.$dropdown.on('change', '.filter-turnover-min, .filter-turnover-max', function() {
                var $row = $(this).closest('.filter-row');
                self.filters.turnoverMin = $row.find('.filter-turnover-min').val() || null;
                self.filters.turnoverMax = $row.find('.filter-turnover-max').val() || null;
                self.refreshSearch();
            });

            // Entity-specific filters: Date added range
            this.$dropdown.on('change', '.filter-date-add-from, .filter-date-add-to', function() {
                var $row = $(this).closest('.filter-row');
                self.filters.dateAddFrom = $row.find('.filter-date-add-from').val() || null;
                self.filters.dateAddTo = $row.find('.filter-date-add-to').val() || null;
                self.refreshSearch();
            });

            // Entity-specific filters: Last product date range
            this.$dropdown.on('change', '.filter-last-product-from, .filter-last-product-to', function() {
                var $row = $(this).closest('.filter-row');
                self.filters.lastProductFrom = $row.find('.filter-last-product-from').val() || null;
                self.filters.lastProductTo = $row.find('.filter-last-product-to').val() || null;
                self.refreshSearch();
            });

            // Entity-specific filters: Depth (categories)
            this.$dropdown.on('change', '.filter-depth-select', function() {
                self.filters.depth = $(this).val() || null;
                self.refreshSearch();
            });

            // Entity-specific filters: Has products (categories)
            this.$dropdown.on('change', '.filter-has-products', function() {
                self.filters.hasProducts = $(this).is(':checked');
                self.refreshSearch();
            });

            // Entity-specific filters: Has description (categories)
            this.$dropdown.on('change', '.filter-has-description', function() {
                self.filters.hasDescription = $(this).is(':checked');
                self.refreshSearch();
            });

            // Entity-specific filters: Has image (categories)
            this.$dropdown.on('change', '.filter-has-image', function() {
                self.filters.hasImage = $(this).is(':checked');
                self.refreshSearch();
            });

            // Entity-specific filters: Active only
            this.$dropdown.on('change', '.filter-active-only', function() {
                self.filters.activeOnly = $(this).is(':checked');
                self.refreshSearch();
            });

            // Entity-specific filters: Attribute group select
            this.$dropdown.on('change', '.filter-attribute-group-select', function() {
                self.filters.attributeGroup = $(this).val() || null;
                self.refreshSearch();
            });

            // Entity-specific filters: Feature group select
            this.$dropdown.on('change', '.filter-feature-group-select', function() {
                self.filters.featureGroup = $(this).val() || null;
                self.refreshSearch();
            });

            // Entity-specific filters: Color attributes only
            this.$dropdown.on('change', '.filter-is-color', function() {
                self.filters.isColor = $(this).is(':checked');
                self.refreshSearch();
            });

            // Entity-specific filters: Custom feature values only
            this.$dropdown.on('change', '.filter-is-custom', function() {
                self.filters.isCustom = $(this).is(':checked');
                self.refreshSearch();
            });

            // Entity-specific filters: CMS indexable
            this.$dropdown.on('change', '.filter-indexable', function() {
                self.filters.indexable = $(this).is(':checked');
                self.refreshSearch();
            });

            // Entity-specific filters: Countries - Has holidays
            this.$dropdown.on('change', '.filter-has-holidays', function() {
                self.filters.hasHolidays = $(this).is(':checked');
                self.refreshSearch();
            });

            // Entity-specific filters: Countries - Contains states
            this.$dropdown.on('change', '.filter-contains-states', function() {
                self.filters.containsStates = $(this).is(':checked');
                self.refreshSearch();
            });

            // Entity-specific filters: Countries - Zone select
            this.$dropdown.on('change', '.filter-zone-select', function() {
                self.filters.zone = $(this).val() || null;
                self.refreshSearch();
            });

            // Clear entity-specific filters
            this.$dropdown.on('click', '.filter-row-entity-categories .btn-clear-filters, .filter-row-entity-manufacturers .btn-clear-filters, .filter-row-entity-suppliers .btn-clear-filters, .filter-row-entity-attributes .btn-clear-filters, .filter-row-entity-features .btn-clear-filters, .filter-row-entity-cms .btn-clear-filters, .filter-row-entity-cms-categories .btn-clear-filters, .filter-row-entity-countries .btn-clear-filters', function(e) {
                e.preventDefault();
                var $row = $(this).closest('.filter-row');
                $row.find('input[type="number"]').val('');
                $row.find('input[type="date"]').val('');
                $row.find('select').val('');
                $row.find('input[type="checkbox"]').prop('checked', false);
                $row.find('.filter-active-only').prop('checked', true);

                self.filters.productCountMin = null;
                self.filters.productCountMax = null;
                self.filters.salesMin = null;
                self.filters.salesMax = null;
                self.filters.turnoverMin = null;
                self.filters.turnoverMax = null;
                self.filters.depth = null;
                self.filters.hasProducts = false;
                self.filters.hasDescription = false;
                self.filters.hasImage = false;
                self.filters.activeOnly = true;
                self.filters.attributeGroup = null;
                self.filters.featureGroup = null;
                self.filters.isColor = false;
                self.filters.isCustom = false;
                self.filters.indexable = false;
                self.filters.dateAddFrom = null;
                self.filters.dateAddTo = null;
                self.filters.lastProductFrom = null;
                self.filters.lastProductTo = null;
                // Country filters
                self.filters.hasHolidays = false;
                self.filters.containsStates = false;
                self.filters.zone = null;
                self.refreshSearch();
            });

            // Toggle filter group - show values
            this.$dropdown.on('click', '.filter-group-toggle', function(e) {
                // Ignore clicks on the preview badge
                if ($(e.target).closest('.toggle-count.clickable').length) {
                    return;
                }
                e.preventDefault();
                var $btn = $(this);
                var groupId = $btn.data('group-id');
                var type = $btn.data('type');
                var isActive = $btn.hasClass('active');

                self.$dropdown.find('.filter-group-toggle').removeClass('active');

                if (isActive) {
                    self.hideFilterGroupValues();
                } else {
                    $btn.addClass('active');
                    self.showFilterGroupValues(groupId, type);
                }
            });

            // Filter group toggle count badge click for preview popover
            this.$dropdown.on('click', '.filter-group-toggle .toggle-count.clickable', function(e) {
                e.stopPropagation();
                e.preventDefault();

                var $badge = $(this);
                var groupId = $badge.data('groupId');
                var groupType = $badge.data('type');
                var groupName = $badge.data('groupName');

                if ($badge.hasClass('popover-open')) {
                    self.hidePreviewPopover();
                } else {
                    self.showFilterGroupPreviewPopover($badge, groupId, groupType, groupName);
                }
            });

            // View mode select change
            this.$dropdown.on('change', '.view-mode-select', function() {
                var mode = $(this).val();
                var prevMode = self.viewMode;
                self.viewMode = mode;

                // Remove all view mode classes and add the new one
                self.$dropdown
                    .removeClass('view-list view-tree view-cols-2 view-cols-3 view-cols-4 view-cols-5 view-cols-6 view-cols-7 view-cols-8')
                    .addClass('view-' + mode.replace('cols-', 'cols-'));

                // For tree view, load the category tree (only for categories/cms_categories)
                var searchEntity = self.activeGroup ? self.activeGroup.searchEntity : '';
                if (mode === 'tree' && (searchEntity === 'categories' || searchEntity === 'cms_categories')) {
                    self.loadCategoryTree();
                } else if (mode !== 'tree') {
                    // If switching FROM tree mode, need to refresh search to load data
                    if (prevMode === 'tree') {
                        self.refreshSearch();
                    } else {
                        // Re-render current results with new view mode
                        self.renderSearchResults(false);
                    }
                }
            });

            // Close values row
            this.$dropdown.on('click', '.btn-close-values', function(e) {
                e.preventDefault();
                e.stopPropagation();
                self.hideFilterGroupValues();
            });

            // Toggle filter chip (attribute/feature value)
            this.$dropdown.on('click', '.filter-chip', function(e) {
                e.preventDefault();
                e.stopPropagation();

                var $chip = $(this);
                var id = parseInt($chip.data('id'), 10);
                var isAttribute = $chip.hasClass('filter-attr-chip');
                var filterArray = isAttribute ? self.filters.attributes : self.filters.features;

                var index = filterArray.indexOf(id);
                if (index === -1) {
                    filterArray.push(id);
                    $chip.addClass('active');
                } else {
                    filterArray.splice(index, 1);
                    $chip.removeClass('active');
                }

                self.updateFilterToggleStates();
                self.refreshSearch();
            });

            // Clear all filters
            this.$dropdown.on('click', '.filter-row-quick .btn-clear-filters', function(e) {
                e.preventDefault();
                self.clearFilters();
            });

            // Click outside to close
            $(document).on('click', function(e) {
                if (!$(e.target).closest('.value-picker').length &&
                    !$(e.target).closest('.target-search-dropdown').length &&
                    !$(e.target).closest('.target-preview-popover').length) {
                    self.hideDropdown();
                }
            });

            // Keyboard shortcuts
            $(document).on('keydown', function(e) {
                if (!self.$dropdown || !self.$dropdown.hasClass('show')) return;

                // Allow default behavior in input/textarea fields
                var isInputFocused = $(document.activeElement).is('input, textarea');

                // Ctrl+A / Cmd+A - Select All (only when not in input)
                if ((e.ctrlKey || e.metaKey) && e.keyCode === 65) {
                    if (isInputFocused) return; // Let browser select text
                    e.preventDefault();
                    e.stopPropagation();
                    self.$dropdown.find('.btn-select-all').trigger('click');
                    return false;
                }

                // Ctrl+D / Cmd+D - Clear/Deselect all (only when not in input)
                if ((e.ctrlKey || e.metaKey) && e.keyCode === 68) {
                    if (isInputFocused) return; // Let browser handle
                    e.preventDefault();
                    e.stopPropagation();
                    self.$dropdown.find('.btn-clear-selection').trigger('click');
                    return false;
                }

                if (e.key === 'Escape') {
                    e.preventDefault();
                    self.hideDropdown();
                } else if (e.key === 'Enter') {
                    e.preventDefault();
                    self.hideDropdown();
                }
            });

        }
    };

})(jQuery);

/**
 * Entity Selector - Dropdown Module
 * Search dropdown UI creation and positioning
 * @partial _dropdown.js
 */

(function($) {
    'use strict';

    window._EntitySelectorMixins = window._EntitySelectorMixins || {};

    window._EntitySelectorMixins.dropdown = {

        createDropdown: function() {
            this.$wrapper.find('.target-search-dropdown').remove();

            var trans = this.config.trans || {};

            var html = '<div class="target-search-dropdown view-list">';

            // Header with results count, actions, sort controls, view mode
            html += '<div class="dropdown-header">';
            html += '<span class="results-count">0 results</span>';

            html += '<div class="dropdown-actions">';

            // Select all / Clear buttons with keyboard shortcuts
            html += '<button type="button" class="btn-select-all" title="' + (trans.select_all || 'Select all visible') + '">';
            html += this.esIcon('check_box') + ' ' + (trans.all || 'All') + ' <kbd>Ctrl+A</kbd>';
            html += '</button>';
            html += '<button type="button" class="btn-clear-selection" title="' + (trans.clear_selection || 'Clear selection') + '">';
            html += this.esIcon('check_box_outline_blank') + ' ' + (trans.clear || 'Clear') + ' <kbd>Ctrl+D</kbd>';
            html += '</button>';

            // Sort controls - options with data-entities attribute for entity-specific filtering
            html += '<div class="sort-controls">';
            html += '<select class="sort-field-select" title="Sort by">';
            // Universal options (all entities)
            html += '<option value="name">' + (trans.sort_name || 'Name') + '</option>';
            html += '<option value="id">' + (trans.sort_id || 'ID') + '</option>';
            html += '<option value="selected">' + (trans.sort_selected || 'Selected') + '</option>';
            // Product-specific
            html += '<option value="price" data-entities="products">' + (trans.sort_price || 'Price') + '</option>';
            html += '<option value="stock" data-entities="products">' + (trans.sort_stock || 'Stock') + '</option>';
            html += '<option value="popularity" data-entities="products">' + (trans.sort_popularity || 'Sales') + '</option>';
            html += '<option value="reference" data-entities="products">' + (trans.sort_reference || 'Reference') + '</option>';
            // Position-based entities (categories, cms, cms_categories, attributes)
            html += '<option value="position" data-entities="categories,cms,cms_categories,attributes">' + (trans.sort_position || 'Position') + '</option>';
            // Product count (categories, manufacturers, suppliers)
            html += '<option value="product_count" data-entities="categories,manufacturers,suppliers">' + (trans.sort_product_count || 'Products') + '</option>';
            html += '</select>';
            html += '<button type="button" class="btn-sort-dir" data-dir="ASC" title="Sort direction">';
            html += this.esIcon('sort_by_alpha');
            html += '</button>';

            // View mode selector - Tree option always present, shown for categories
            html += '<select class="view-mode-select" title="View mode">';
            html += '<option value="list">' + (trans.view_list || 'List') + '</option>';
            html += '<option value="tree" class="tree-view-option">' + (trans.view_tree || 'Tree') + '</option>';
            html += '<option value="cols-2">2 ' + (trans.cols || 'cols') + '</option>';
            html += '<option value="cols-3">3 ' + (trans.cols || 'cols') + '</option>';
            html += '<option value="cols-4">4 ' + (trans.cols || 'cols') + '</option>';
            html += '<option value="cols-5">5 ' + (trans.cols || 'cols') + '</option>';
            html += '<option value="cols-6">6 ' + (trans.cols || 'cols') + '</option>';
            html += '<option value="cols-7">7 ' + (trans.cols || 'cols') + '</option>';
            html += '<option value="cols-8">8 ' + (trans.cols || 'cols') + '</option>';
            html += '</select>';
            html += '</div>'; // End sort-controls

            // Refine search
            html += '<div class="refine-compact">';
            html += '<button type="button" class="btn-refine-negate" title="' + (trans.exclude_matches || 'Exclude matches (NOT contains)') + '">' + this.esIcon('block') + '</button>';
            html += '<input type="text" class="refine-input" placeholder="' + (trans.refine_short || 'Refine...') + '">';
            html += '<button type="button" class="btn-clear-refine" style="display:none;">' + this.esIcon('close') + '</button>';
            html += '</div>';

            // Filter toggle button
            html += '<button type="button" class="btn-toggle-filters" title="' + (trans.toggle_filters || 'Filters') + '">';
            html += this.esIcon('filter_list');
            html += '</button>';

            // History button
            html += '<button type="button" class="btn-show-history" title="' + (trans.recent_searches || 'Recent searches') + '">';
            html += this.esIcon('schedule');
            html += '</button>';

            html += '</div>'; // End dropdown-actions
            html += '</div>'; // End dropdown-header

            // Filter panel
            html += '<div class="filter-panel">';

            // Quick filters row (for products)
            html += '<div class="filter-row filter-row-quick" data-entity="products">';
            html += '<label class="filter-label"><input type="checkbox" class="filter-in-stock"> ' + (trans.in_stock || 'In stock') + '</label>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-discounted"> ' + (trans.discounted || 'On sale') + '</label>';

            // Price range
            html += '<div class="filter-price-range">';
            html += '<span class="filter-price-label">' + (trans.price || 'Price') + ':</span>';
            html += '<input type="number" class="filter-price-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="0.01">';
            html += '<span class="filter-price-sep">-</span>';
            html += '<input type="number" class="filter-price-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="0.01">';
            html += '</div>';

            html += '<button type="button" class="btn-clear-filters" title="' + (trans.clear_filters || 'Clear filters') + '">';
            html += this.esIcon('close');
            html += '</button>';
            html += '</div>';

            // Attribute/Feature filter toggles for products
            html += '<div class="filter-row filter-row-attributes" data-entity="products" style="display:none;">';
            html += '<span class="filter-row-label">' + this.esIcon('label') + ' ' + (trans.attributes || 'Attributes') + ':</span>';
            html += '<div class="filter-attributes-container"></div>';
            html += '</div>';
            html += '<div class="filter-row filter-row-values filter-row-attr-values" data-type="attribute" style="display:none;">';
            html += '<div class="filter-values-container"></div>';
            html += '</div>';

            html += '<div class="filter-row filter-row-features" data-entity="products" style="display:none;">';
            html += '<span class="filter-row-label">' + this.esIcon('list') + ' ' + (trans.features || 'Features') + ':</span>';
            html += '<div class="filter-features-container"></div>';
            html += '</div>';
            html += '<div class="filter-row filter-row-values filter-row-feat-values" data-type="feature" style="display:none;">';
            html += '<div class="filter-values-container"></div>';
            html += '</div>';

            // Entity-specific filters: Categories
            html += '<div class="filter-row filter-row-entity-categories filter-row-multi" data-entity="categories" style="display:none;">';
            html += '<div class="filter-subrow">';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
            html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
            html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
            html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
            html += '</div>';
            html += '<div class="filter-subrow">';
            html += '<div class="filter-select-group">';
            html += '<span class="filter-select-label">' + this.esIcon('account_tree') + ' ' + (trans.depth || 'Depth') + ':</span>';
            html += '<select class="filter-depth-select">';
            html += '<option value="">' + (trans.all_levels || 'All levels') + '</option>';
            html += '<option value="1">' + (trans.level || 'Level') + ' 1 (' + (trans.root || 'Root') + ')</option>';
            html += '<option value="2">' + (trans.level || 'Level') + ' 2</option>';
            html += '<option value="3">' + (trans.level || 'Level') + ' 3</option>';
            html += '<option value="4">' + (trans.level || 'Level') + ' 4+</option>';
            html += '</select>';
            html += '</div>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-has-products"> ' + (trans.has_products || 'Has products') + '</label>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-has-description"> ' + (trans.has_description || 'Has description') + '</label>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-has-image"> ' + (trans.has_image || 'Has image') + '</label>';
            html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
            html += '</div>';
            html += '</div>';

            // Entity-specific filters: Manufacturers
            html += '<div class="filter-row filter-row-entity-manufacturers filter-row-multi" data-entity="manufacturers" style="display:none;">';
            html += '<div class="filter-subrow">';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
            html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
            html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
            html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
            html += '</div>';
            html += '<div class="filter-subrow">';
            html += '<div class="filter-date-group">';
            html += '<span class="filter-date-label">' + this.esIcon('event') + ' ' + (trans.date_added || 'Added') + ':</span>';
            html += '<input type="date" class="filter-date-add-from" title="' + (trans.from || 'From') + '">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="date" class="filter-date-add-to" title="' + (trans.to || 'To') + '">';
            html += '</div>';
            html += '<div class="filter-date-group">';
            html += '<span class="filter-date-label">' + this.esIcon('schedule') + ' ' + (trans.last_product || 'Last product') + ':</span>';
            html += '<input type="date" class="filter-last-product-from" title="' + (trans.from || 'From') + '">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="date" class="filter-last-product-to" title="' + (trans.to || 'To') + '">';
            html += '</div>';
            html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
            html += '</div>';
            html += '</div>';

            // Entity-specific filters: Suppliers
            html += '<div class="filter-row filter-row-entity-suppliers filter-row-multi" data-entity="suppliers" style="display:none;">';
            html += '<div class="filter-subrow">';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
            html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
            html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
            html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
            html += '</div>';
            html += '<div class="filter-subrow">';
            html += '<div class="filter-date-group">';
            html += '<span class="filter-date-label">' + this.esIcon('event') + ' ' + (trans.date_added || 'Added') + ':</span>';
            html += '<input type="date" class="filter-date-add-from" title="' + (trans.from || 'From') + '">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="date" class="filter-date-add-to" title="' + (trans.to || 'To') + '">';
            html += '</div>';
            html += '<div class="filter-date-group">';
            html += '<span class="filter-date-label">' + this.esIcon('schedule') + ' ' + (trans.last_product || 'Last product') + ':</span>';
            html += '<input type="date" class="filter-last-product-from" title="' + (trans.from || 'From') + '">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="date" class="filter-last-product-to" title="' + (trans.to || 'To') + '">';
            html += '</div>';
            html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
            html += '</div>';
            html += '</div>';

            // Entity-specific filters: Attributes
            html += '<div class="filter-row filter-row-entity-attributes filter-row-multi" data-entity="attributes" style="display:none;">';
            html += '<div class="filter-subrow">';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
            html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
            html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
            html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '</div>';
            html += '<div class="filter-subrow">';
            html += '<div class="filter-select-group">';
            html += '<span class="filter-select-label">' + this.esIcon('label') + ' ' + (trans.attribute_group || 'Group') + ':</span>';
            html += '<select class="filter-attribute-group-select">';
            html += '<option value="">' + (trans.all_groups || 'All groups') + '</option>';
            html += '</select>';
            html += '</div>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-is-color"> ' + (trans.color_only || 'Color attributes') + '</label>';
            html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
            html += '</div>';
            html += '</div>';

            // Entity-specific filters: Features
            html += '<div class="filter-row filter-row-entity-features filter-row-multi" data-entity="features" style="display:none;">';
            html += '<div class="filter-subrow">';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('inventory_2') + ' ' + (trans.product_count || 'Products') + ':</span>';
            html += '<input type="number" class="filter-product-count-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-product-count-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('shopping_cart') + ' ' + (trans.total_sales || 'Sales') + ':</span>';
            html += '<input type="number" class="filter-sales-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-sales-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '<div class="filter-range-group">';
            html += '<span class="filter-range-label">' + this.esIcon('payments') + ' ' + (trans.turnover || 'Revenue') + ':</span>';
            html += '<input type="number" class="filter-turnover-min" placeholder="' + (trans.min || 'Min') + '" min="0" step="1">';
            html += '<span class="filter-range-sep">-</span>';
            html += '<input type="number" class="filter-turnover-max" placeholder="' + (trans.max || 'Max') + '" min="0" step="1">';
            html += '</div>';
            html += '</div>';
            html += '<div class="filter-subrow">';
            html += '<div class="filter-select-group">';
            html += '<span class="filter-select-label">' + this.esIcon('list') + ' ' + (trans.feature_group || 'Group') + ':</span>';
            html += '<select class="filter-feature-group-select">';
            html += '<option value="">' + (trans.all_groups || 'All groups') + '</option>';
            html += '</select>';
            html += '</div>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-is-custom"> ' + (trans.custom_only || 'Custom values') + '</label>';
            html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
            html += '</div>';
            html += '</div>';

            // Entity-specific filters: CMS Pages
            html += '<div class="filter-row filter-row-entity-cms" data-entity="cms" style="display:none;">';
            html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-indexable"> ' + (trans.indexable || 'Indexable') + '</label>';
            html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
            html += '</div>';

            // Entity-specific filters: CMS Categories
            html += '<div class="filter-row filter-row-entity-cms-categories" data-entity="cms_categories" style="display:none;">';
            html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
            html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
            html += '</div>';

            // Entity-specific filters: Countries
            html += '<div class="filter-row filter-row-entity-countries" data-entity="countries" style="display:none;">';
            html += '<label class="filter-label"><input type="checkbox" class="filter-active-only" checked> ' + (trans.active_only || 'Active only') + '</label>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-has-holidays"> ' + (trans.has_holidays || 'Has holidays') + '</label>';
            html += '<label class="filter-label"><input type="checkbox" class="filter-contains-states"> ' + (trans.contains_states || 'Has states') + '</label>';
            html += '<div class="filter-select-group">';
            html += '<span class="filter-select-label">' + this.esIcon('language') + ' ' + (trans.zone || 'Zone') + ':</span>';
            html += '<select class="filter-zone-select">';
            html += '<option value="">' + (trans.all_zones || 'All zones') + '</option>';
            html += '</select>';
            html += '</div>';
            html += '<button type="button" class="btn-clear-filters">' + this.esIcon('close') + '</button>';
            html += '</div>';

            html += '</div>'; // End filter-panel

            // Results header for list view (product columns)
            html += '<div class="results-header">';
            html += '<span class="header-spacer"></span>';
            html += '<span class="header-col header-col-name">' + (trans.product || 'Product') + '</span>';
            html += '<span class="header-col header-col-price">' + (trans.price || 'Price') + '</span>';
            html += '<span class="header-col header-col-sale">' + (trans.sale || 'Sale') + '</span>';
            html += '<span class="header-col header-col-stock">' + (trans.stock || 'Stock') + '</span>';
            html += '<span class="header-col header-col-sales">' + (trans.sold || 'Sold') + '</span>';
            html += '</div>';

            // Results
            html += '<div class="dropdown-results"></div>';

            // Footer - unified load more + actions
            html += '<div class="dropdown-footer">';

            // Left side: load more
            html += '<div class="dropdown-footer-left" style="visibility:hidden;">';
            html += '<span class="load-label">' + (trans.load || 'Load') + '</span>';
            html += '<select class="load-more-select">';
            html += '<option value="20">20</option>';
            html += '<option value="50">50</option>';
            html += '<option value="100">100</option>';
            html += '<option value="all">' + (trans.all || 'All') + '</option>';
            html += '</select>';
            html += '<span class="remaining-text">' + (trans.of || 'of') + ' <strong class="remaining-count">0</strong> ' + (trans.remaining || 'remaining') + '</span>';
            html += '</div>';

            // Right side: action buttons
            html += '<div class="dropdown-footer-right">';
            html += '<button type="button" class="dropdown-action-btn btn-cancel">' + this.esIcon('close') + ' ' + (trans.cancel || 'Cancel') + ' <span class="btn-shortcut">Esc</span></button>';
            html += '<button type="button" class="dropdown-action-btn btn-save">' + this.esIcon('check') + ' ' + (trans.save || 'Save') + ' <span class="btn-shortcut">⏎</span></button>';
            html += '</div>';

            html += '</div>';

            html += '</div>';

            this.$dropdown = $(html);
            $('body').append(this.$dropdown);
        },

        hideDropdown: function() {
            if (this.$dropdown) {
                this.$dropdown.removeClass('show');
            }
            this.activeGroup = null;
        },

        positionDropdown: function($input) {
            if (!this.$dropdown) return;

            var $picker = $input.closest('.value-picker');
            var $searchBox = $input.closest('.entity-search-box');

            // Get absolute positions (dropdown is appended to body)
            var searchBoxOffset = $searchBox.offset();
            var searchBoxHeight = $searchBox.outerHeight();
            var pickerOffset = $picker.offset();
            var pickerWidth = $picker.outerWidth();

            // Calculate position relative to document
            var dropdownTop = searchBoxOffset.top + searchBoxHeight + 4;
            var dropdownLeft = pickerOffset.left;
            var dropdownWidth = Math.max(pickerWidth, 400);

            // Ensure dropdown doesn't overflow the viewport horizontally
            var viewportWidth = $(window).width();
            if (dropdownLeft + dropdownWidth > viewportWidth - 10) {
                dropdownWidth = viewportWidth - dropdownLeft - 10;
            }

            // Ensure dropdown doesn't overflow viewport vertically
            var viewportHeight = $(window).height();
            var scrollTop = $(window).scrollTop();
            var maxHeight = viewportHeight - (dropdownTop - scrollTop) - 20;
            maxHeight = Math.max(maxHeight, 400);

            this.$dropdown.css({
                position: 'absolute',
                top: dropdownTop,
                left: dropdownLeft,
                width: dropdownWidth,
                maxHeight: maxHeight,
                zIndex: 10000
            });

            // Show the dropdown
            this.$dropdown.addClass('show');
        }
    };

})(jQuery);

/**
 * Entity Selector - Search Module
 * AJAX search, results rendering, category tree, filters, search history
 * @partial _search.js
 */

(function($) {
    'use strict';

    window._EntitySelectorMixins = window._EntitySelectorMixins || {};

    window._EntitySelectorMixins.search = {

        // Category tree cache
        categoryTreeCache: null,

        /**
         * Perform AJAX search for entities
         */
        performSearch: function(appendMode) {
            var self = this;

            if (!this.activeGroup) return;

            this.isLoading = true;

            var searchEntity = this.activeGroup.searchEntity;

            // Build request data with sort and filter params
            var limit = appendMode && this.loadMoreCount ? this.loadMoreCount : 20;
            var requestData = {
                ajax: 1,
                action: 'searchTargetEntities',
                trait: 'EntitySelector',
                entity_type: searchEntity,
                q: this.searchQuery,
                limit: limit,
                offset: appendMode ? this.searchOffset : 0,
                sort_by: this.currentSort ? this.currentSort.field : 'name',
                sort_dir: this.currentSort ? this.currentSort.dir : 'ASC'
            };

            // Add refine query if present
            if (this.refineQuery) {
                requestData.refine = this.refineQuery;
                if (this.refineNegate) {
                    requestData.refine_negate = 1;
                }
            }

            // Add product-specific filters
            if (searchEntity === 'products' && this.filters) {
                if (this.filters.inStock) {
                    requestData.filter_in_stock = 1;
                }
                if (this.filters.discounted) {
                    requestData.filter_discounted = 1;
                }
                if (this.filters.priceMin !== null && this.filters.priceMin !== '') {
                    requestData.filter_price_min = this.filters.priceMin;
                }
                if (this.filters.priceMax !== null && this.filters.priceMax !== '') {
                    requestData.filter_price_max = this.filters.priceMax;
                }
                if (this.filters.attributes && this.filters.attributes.length > 0) {
                    requestData.filter_attributes = JSON.stringify(this.filters.attributes);
                }
                if (this.filters.features && this.filters.features.length > 0) {
                    requestData.filter_features = JSON.stringify(this.filters.features);
                }
            }

            // Add entity-specific filters for non-product entities
            if (searchEntity !== 'products' && this.filters) {
                // Product count range (categories, manufacturers, suppliers, attributes, features)
                if (this.filters.productCountMin !== null && this.filters.productCountMin !== '') {
                    requestData.filter_product_count_min = this.filters.productCountMin;
                }
                if (this.filters.productCountMax !== null && this.filters.productCountMax !== '') {
                    requestData.filter_product_count_max = this.filters.productCountMax;
                }

                // Category-specific
                if (searchEntity === 'categories') {
                    if (this.filters.depth) {
                        requestData.filter_depth = this.filters.depth;
                    }
                    if (this.filters.hasProducts) {
                        requestData.filter_has_products = 1;
                    }
                    if (this.filters.hasDescription) {
                        requestData.filter_has_description = 1;
                    }
                    if (this.filters.hasImage) {
                        requestData.filter_has_image = 1;
                    }
                    if (this.filters.salesMin !== null && this.filters.salesMin !== '') {
                        requestData.filter_sales_min = this.filters.salesMin;
                    }
                    if (this.filters.salesMax !== null && this.filters.salesMax !== '') {
                        requestData.filter_sales_max = this.filters.salesMax;
                    }
                    if (this.filters.turnoverMin !== null && this.filters.turnoverMin !== '') {
                        requestData.filter_turnover_min = this.filters.turnoverMin;
                    }
                    if (this.filters.turnoverMax !== null && this.filters.turnoverMax !== '') {
                        requestData.filter_turnover_max = this.filters.turnoverMax;
                    }
                    if (this.filters.activeOnly) {
                        requestData.filter_active = 1;
                    }
                }

                // Manufacturer-specific
                if (searchEntity === 'manufacturers') {
                    if (this.filters.salesMin !== null && this.filters.salesMin !== '') {
                        requestData.filter_sales_min = this.filters.salesMin;
                    }
                    if (this.filters.salesMax !== null && this.filters.salesMax !== '') {
                        requestData.filter_sales_max = this.filters.salesMax;
                    }
                    if (this.filters.turnoverMin !== null && this.filters.turnoverMin !== '') {
                        requestData.filter_turnover_min = this.filters.turnoverMin;
                    }
                    if (this.filters.turnoverMax !== null && this.filters.turnoverMax !== '') {
                        requestData.filter_turnover_max = this.filters.turnoverMax;
                    }
                    if (this.filters.dateAddFrom) {
                        requestData.filter_date_add_from = this.filters.dateAddFrom;
                    }
                    if (this.filters.dateAddTo) {
                        requestData.filter_date_add_to = this.filters.dateAddTo;
                    }
                    if (this.filters.lastProductFrom) {
                        requestData.filter_last_product_from = this.filters.lastProductFrom;
                    }
                    if (this.filters.lastProductTo) {
                        requestData.filter_last_product_to = this.filters.lastProductTo;
                    }
                    if (this.filters.activeOnly) {
                        requestData.filter_active = 1;
                    }
                }

                // Supplier-specific
                if (searchEntity === 'suppliers') {
                    if (this.filters.salesMin !== null && this.filters.salesMin !== '') {
                        requestData.filter_sales_min = this.filters.salesMin;
                    }
                    if (this.filters.salesMax !== null && this.filters.salesMax !== '') {
                        requestData.filter_sales_max = this.filters.salesMax;
                    }
                    if (this.filters.turnoverMin !== null && this.filters.turnoverMin !== '') {
                        requestData.filter_turnover_min = this.filters.turnoverMin;
                    }
                    if (this.filters.turnoverMax !== null && this.filters.turnoverMax !== '') {
                        requestData.filter_turnover_max = this.filters.turnoverMax;
                    }
                    if (this.filters.dateAddFrom) {
                        requestData.filter_date_add_from = this.filters.dateAddFrom;
                    }
                    if (this.filters.dateAddTo) {
                        requestData.filter_date_add_to = this.filters.dateAddTo;
                    }
                    if (this.filters.lastProductFrom) {
                        requestData.filter_last_product_from = this.filters.lastProductFrom;
                    }
                    if (this.filters.lastProductTo) {
                        requestData.filter_last_product_to = this.filters.lastProductTo;
                    }
                    if (this.filters.activeOnly) {
                        requestData.filter_active = 1;
                    }
                }

                // Attribute-specific
                if (searchEntity === 'attributes') {
                    if (this.filters.salesMin !== null && this.filters.salesMin !== '') {
                        requestData.filter_sales_min = this.filters.salesMin;
                    }
                    if (this.filters.salesMax !== null && this.filters.salesMax !== '') {
                        requestData.filter_sales_max = this.filters.salesMax;
                    }
                    if (this.filters.turnoverMin !== null && this.filters.turnoverMin !== '') {
                        requestData.filter_turnover_min = this.filters.turnoverMin;
                    }
                    if (this.filters.turnoverMax !== null && this.filters.turnoverMax !== '') {
                        requestData.filter_turnover_max = this.filters.turnoverMax;
                    }
                    if (this.filters.attributeGroup) {
                        requestData.filter_attribute_group = this.filters.attributeGroup;
                    }
                    if (this.filters.isColor) {
                        requestData.filter_is_color = 1;
                    }
                }

                // Feature-specific
                if (searchEntity === 'features') {
                    if (this.filters.salesMin !== null && this.filters.salesMin !== '') {
                        requestData.filter_sales_min = this.filters.salesMin;
                    }
                    if (this.filters.salesMax !== null && this.filters.salesMax !== '') {
                        requestData.filter_sales_max = this.filters.salesMax;
                    }
                    if (this.filters.turnoverMin !== null && this.filters.turnoverMin !== '') {
                        requestData.filter_turnover_min = this.filters.turnoverMin;
                    }
                    if (this.filters.turnoverMax !== null && this.filters.turnoverMax !== '') {
                        requestData.filter_turnover_max = this.filters.turnoverMax;
                    }
                    if (this.filters.featureGroup) {
                        requestData.filter_feature_group = this.filters.featureGroup;
                    }
                    if (this.filters.isCustom) {
                        requestData.filter_is_custom = 1;
                    }
                }

                // CMS-specific
                if (searchEntity === 'cms') {
                    if (this.filters.activeOnly) {
                        requestData.filter_active = 1;
                    }
                    if (this.filters.indexable) {
                        requestData.filter_indexable = 1;
                    }
                }

                // CMS Categories-specific
                if (searchEntity === 'cms_categories') {
                    if (this.filters.activeOnly) {
                        requestData.filter_active = 1;
                    }
                }

                // Countries-specific
                if (searchEntity === 'countries') {
                    if (this.filters.activeOnly) {
                        requestData.filter_active = 1;
                    }
                    if (this.filters.hasHolidays) {
                        requestData.filter_has_holidays = 1;
                    }
                    if (this.filters.containsStates) {
                        requestData.filter_contains_states = 1;
                    }
                    if (this.filters.zone) {
                        requestData.filter_zone = this.filters.zone;
                    }
                }
            }

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: requestData,
                success: function(response) {
                    self.isLoading = false;

                    if (!response.success) return;

                    // Save to search history if query is not empty and has results
                    if (self.searchQuery && self.searchQuery.length >= 2 && response.total > 0) {
                        self.addToSearchHistory(searchEntity, self.searchQuery);
                    }

                    if (appendMode) {
                        self.searchResults = self.searchResults.concat(response.results || []);
                    } else {
                        self.searchResults = response.results || [];
                    }
                    self.searchTotal = response.total || 0;
                    self.searchOffset = appendMode ? self.searchOffset + (response.results || []).length : (response.results || []).length;

                    self.renderSearchResults(appendMode);
                    self.$dropdown.addClass('show');
                },
                error: function() {
                    self.isLoading = false;
                }
            });
        },

        /**
         * Render search results in the dropdown
         */
        renderSearchResults: function(appendMode) {
            var self = this;
            var trans = this.config.trans || {};
            var $container = this.$dropdown.find('.dropdown-results');

            // Get selected IDs from current picker (to mark as selected)
            // and hidden IDs from sibling exclude pickers with same entity type (to hide completely)
            var selectedIds = [];
            var hiddenIds = [];
            if (this.activeGroup) {
                var $block = this.$wrapper.find('.target-block[data-block-type="' + this.activeGroup.blockType + '"]');
                var $group = $block.find('.selection-group[data-group-index="' + this.activeGroup.groupIndex + '"]');
                var currentSearchEntity = this.activeGroup.searchEntity;
                var currentExcludeIndex = this.activeGroup.excludeIndex;

                if (this.activeGroup.section === 'include') {
                    // For include section, just get current picker's selections
                    var $picker = $group.find('.include-picker');
                    $picker.find('.entity-chip').each(function() {
                        selectedIds.push(String($(this).data('id')));
                    });
                } else {
                    // For exclude section, get current picker's selections AND
                    // collect IDs from sibling exclude rows with same entity type to hide
                    var $currentExcludeRow = $group.find('.exclude-row[data-exclude-index="' + currentExcludeIndex + '"]');
                    var $currentPicker = $currentExcludeRow.find('.exclude-picker');

                    // Get selected IDs from current exclude row
                    $currentPicker.find('.entity-chip').each(function() {
                        selectedIds.push(String($(this).data('id')));
                    });

                    // Get hidden IDs from OTHER exclude rows with the same entity type
                    $group.find('.exclude-row').each(function() {
                        var $row = $(this);
                        var rowIndex = parseInt($row.data('excludeIndex'), 10);

                        // Skip current exclude row
                        if (rowIndex === currentExcludeIndex) return;

                        var $picker = $row.find('.exclude-picker');
                        var rowEntityType = $picker.attr('data-search-entity') || self.activeGroup.blockType;

                        // Only collect if same entity type
                        if (rowEntityType === currentSearchEntity) {
                            $picker.find('.entity-chip').each(function() {
                                hiddenIds.push(String($(this).data('id')));
                            });
                        }
                    });
                }
            }

            // Check if this is a product search
            var isProductSearch = this.activeGroup && this.activeGroup.searchEntity === 'products';
            var isListView = this.viewMode === 'list';

            // Show/hide results header for products in list view
            this.$dropdown.find('.results-header').toggle(isProductSearch && isListView);

            // Build HTML - filter out items that are hidden (selected in sibling exclude rows)
            var visibleResults = this.searchResults.filter(function(item) {
                return hiddenIds.indexOf(String(item.id)) === -1;
            });

            // Update count (show visible count and total, noting hidden items if any)
            var hiddenCount = this.searchResults.length - visibleResults.length;
            var countText = visibleResults.length + ' / ' + this.searchTotal + ' results';
            if (hiddenCount > 0) {
                countText += ' (' + hiddenCount + ' hidden)';
            }
            this.$dropdown.find('.results-count').text(countText);

            var html = '';
            if (visibleResults.length === 0 && !appendMode) {
                html = '<div class="no-results">' + this.esIcon('search') + ' ' + (trans.no_results || 'No results found') + '</div>';
            } else {
                visibleResults.forEach(function(item) {
                    var isSelected = selectedIds.indexOf(String(item.id)) !== -1;
                    var itemClass = 'dropdown-item' + (isSelected ? ' selected' : '');
                    if (item.type === 'product') itemClass += ' result-item-product';

                    html += '<div class="' + itemClass + '" ';
                    html += 'data-id="' + self.escapeAttr(item.id) + '" ';
                    html += 'data-name="' + self.escapeAttr(item.name) + '"';
                    if (item.image) html += ' data-image="' + self.escapeAttr(item.image) + '"';
                    if (item.subtitle) html += ' data-subtitle="' + self.escapeAttr(item.subtitle) + '"';
                    if (item.iso_code) html += ' data-iso="' + self.escapeAttr(item.iso_code) + '"';
                    html += '>';

                    html += '<span class="result-checkbox">' + self.esIcon('check') + '</span>';

                    var searchEntity = self.activeGroup ? self.activeGroup.searchEntity : null;

                    // Countries show flags
                    if (searchEntity === 'countries' && item.iso_code) {
                        var flagUrl = 'https://flagcdn.com/w40/' + item.iso_code.toLowerCase() + '.png';
                        html += '<div class="result-image result-flag"><img src="' + self.escapeAttr(flagUrl) + '" alt="' + self.escapeAttr(item.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\';"><span class="flag-fallback" style="display:none;">' + self.esIcon('flag') + '</span></div>';
                    } else if (item.image) {
                        html += '<div class="result-image"><img src="' + self.escapeAttr(item.image) + '" alt=""></div>';
                    } else {
                        // Entity-specific icons
                        var iconName = 'widgets'; // default
                        if (searchEntity === 'categories') iconName = 'folder';
                        else if (searchEntity === 'manufacturers') iconName = 'business';
                        else if (searchEntity === 'suppliers') iconName = 'local_shipping';
                        else if (searchEntity === 'attributes') iconName = 'brush';
                        else if (searchEntity === 'features') iconName = 'list';
                        else if (searchEntity === 'cms') iconName = 'description';
                        else if (searchEntity === 'cms_categories') iconName = 'folder';
                        html += '<div class="result-icon">' + self.esIcon(iconName) + '</div>';
                    }

                    html += '<div class="result-info">';
                    html += '<div class="result-name">' + self.escapeHtml(item.name) + '</div>';
                    if (item.subtitle) {
                        // Split multi-line subtitles into separate divs for styling
                        var subtitleLines = item.subtitle.split('\n');
                        html += '<div class="result-subtitle">';
                        subtitleLines.forEach(function(line, idx) {
                            var lineClass = idx === 0 ? 'subtitle-line subtitle-line-primary' : 'subtitle-line subtitle-line-secondary';
                            html += '<div class="' + lineClass + '">' + self.escapeHtml(line) + '</div>';
                        });
                        html += '</div>';
                    }
                    html += '</div>';

                    // Add product-specific columns (price, sale price, stock, sold)
                    if (item.type === 'product') {
                        if (isListView) {
                            // List view: full columns
                            // Regular price
                            html += '<div class="result-col result-col-price">';
                            html += '<span class="col-value">' + (item.regular_price_formatted || item.price_formatted || '') + '</span>';
                            html += '</div>';

                            // Sale price (only if discounted)
                            if (item.has_discount) {
                                html += '<div class="result-col result-col-sale">';
                                html += '<span class="col-value">' + (item.price_formatted || '') + '</span>';
                                html += '</div>';
                            } else {
                                html += '<div class="result-col result-col-sale"></div>';
                            }

                            // Stock column
                            var stockClass = item.stock_status === 'out_of_stock' ? 'stock-out' :
                                             (item.stock_status === 'low_stock' ? 'stock-low' : 'stock-ok');
                            html += '<div class="result-col result-col-stock">';
                            html += '<span class="col-value ' + stockClass + '">' + (item.stock_qty !== undefined ? item.stock_qty : '') + '</span>';
                            html += '</div>';

                            // Sales column
                            html += '<div class="result-col result-col-sales">';
                            html += '<span class="col-value">' + (item.sales_qty !== undefined ? item.sales_qty : '0') + '</span>';
                            html += '</div>';
                        } else {
                            // Grid view: compact info line
                            var gridStockClass = item.stock_status === 'out_of_stock' ? 'stock-out' :
                                                 (item.stock_status === 'low_stock' ? 'stock-low' : '');
                            html += '<div class="result-grid-info">';
                            html += '<span class="grid-price">' + (item.price_formatted || '') + '</span>';
                            if (item.stock_qty !== undefined) {
                                html += '<span class="grid-stock ' + gridStockClass + '">' + item.stock_qty + ' qty</span>';
                            }
                            if (item.has_discount) {
                                html += '<span class="grid-discount">-' + (item.discount_percent || '') + '%</span>';
                            }
                            html += '</div>';
                        }
                    }

                    html += '</div>';
                });
            }

            if (appendMode) {
                $container.append(html);
            } else {
                $container.html(html);
            }

            // Show/hide load more controls and update remaining count
            var hasMore = this.searchResults.length < this.searchTotal;
            var $loadMoreControls = this.$dropdown.find('.load-more-controls');
            $loadMoreControls.toggle(hasMore);

            if (hasMore) {
                var remaining = this.searchTotal - this.searchResults.length;
                $loadMoreControls.find('.remaining-count').text(remaining);

                // Update "All" option in dropdown
                var $select = $loadMoreControls.find('.load-more-select');
                var $allOption = $select.find('option[data-all="true"]');
                if ($allOption.length) {
                    $allOption.val(remaining).text((trans.all || 'All') + ' (' + remaining + ')');
                } else {
                    $select.find('option:last').after('<option value="' + remaining + '" data-all="true">' + (trans.all || 'All') + ' (' + remaining + ')</option>');
                }
            }

            // Ensure dropdown-actions are visible and history button is deactivated
            this.$dropdown.find('.dropdown-actions').show();
            this.$dropdown.find('.btn-show-history').removeClass('active');

            // Disable history button if no search history for current entity type
            var entityType = this.activeGroup ? this.activeGroup.searchEntity : null;
            var hasHistory = entityType && this.getSearchHistory(entityType).length > 0;
            this.$dropdown.find('.btn-show-history').prop('disabled', !hasHistory);
        },

        // NOTE: Tree methods (loadCategoryTree, renderCategoryTree, filterCategoryTree,
        // findTreeDescendants, findTreeAncestors, updateSelectChildrenButtons) are
        // defined in _tree.js which is merged later and takes precedence.

        // =========================================================================
        // Search History
        // =========================================================================

        loadSearchHistory: function() {
            try {
                var stored = localStorage.getItem(this.searchHistoryKey);
                this.searchHistory = stored ? JSON.parse(stored) : {};
            } catch (e) {
                this.searchHistory = {};
            }
        },

        saveSearchHistory: function() {
            try {
                localStorage.setItem(this.searchHistoryKey, JSON.stringify(this.searchHistory));
            } catch (e) {
                // localStorage might be full or unavailable
            }
        },

        addToSearchHistory: function(entityType, query) {
            if (!query || query.length < 2) return;

            if (!this.searchHistory[entityType]) {
                this.searchHistory[entityType] = [];
            }

            var history = this.searchHistory[entityType];

            // Remove if already exists (will re-add at top)
            var existingIndex = history.indexOf(query);
            if (existingIndex !== -1) {
                history.splice(existingIndex, 1);
            }

            // Add at beginning
            history.unshift(query);

            // Trim to max
            if (history.length > this.searchHistoryMax) {
                history = history.slice(0, this.searchHistoryMax);
            }

            this.searchHistory[entityType] = history;
            this.saveSearchHistory();
        },

        removeFromSearchHistory: function(entityType, query) {
            if (!this.searchHistory[entityType]) return;

            var index = this.searchHistory[entityType].indexOf(query);
            if (index !== -1) {
                this.searchHistory[entityType].splice(index, 1);
                this.saveSearchHistory();
            }
        },

        getSearchHistory: function(entityType) {
            return this.searchHistory[entityType] || [];
        },

        showSearchHistory: function(entityType) {
            var history = this.getSearchHistory(entityType);
            var trans = this.config.trans || {};
            var $container = this.$dropdown.find('.dropdown-results');

            // Update header
            this.$dropdown.find('.results-count').text(trans.recent_searches || 'Recent searches');

            // Hide filters, actions, and results header for history view
            this.$dropdown.find('.dropdown-actions').hide();
            this.$dropdown.find('.filter-panel').removeClass('show');
            this.$dropdown.find('.btn-toggle-filters').removeClass('active');
            this.$dropdown.find('.results-header').hide();

            if (!history.length) {
                // No history - just do a regular search
                this.performSearch();
                return;
            }

            // Build history items
            var html = '<div class="search-history-list">';
            for (var i = 0; i < history.length; i++) {
                var query = history[i];
                html += '<div class="history-item" data-query="' + this.escapeAttr(query) + '">';
                html += this.esIcon('schedule');
                html += '<span class="history-query">' + this.escapeHtml(query) + '</span>';
                html += '<button type="button" class="btn-delete-history" title="' + (trans.remove || 'Remove') + '">';
                html += this.esIcon('close');
                html += '</button>';
                html += '</div>';
            }
            html += '</div>';

            $container.html(html);
            this.$dropdown.addClass('show');
        },

        // =========================================================================
        // Filter Methods
        // =========================================================================

        refreshSearch: function() {
            // In tree view mode, re-filter the tree instead of doing a flat AJAX search
            if (this.viewMode === 'tree') {
                this.filterCategoryTree(this.searchQuery || '');
                return;
            }

            this.searchOffset = 0;
            this.loadMoreCount = 20;
            // Reset load more select to default
            if (this.$dropdown) {
                this.$dropdown.find('.load-more-select').val('20');
                // Remove the dynamic "All" option
                this.$dropdown.find('.load-more-select option[data-all="true"]').remove();
            }
            this.performSearch(false);
        },

        clearFilters: function() {
            this.refineQuery = '';
            this.refineNegate = false;
            this.filters = {
                inStock: false,
                discounted: false,
                priceMin: null,
                priceMax: null,
                attributes: [],
                features: [],
                // Entity-specific filters
                productCountMin: null,
                productCountMax: null,
                salesMin: null,
                salesMax: null,
                turnoverMin: null,
                turnoverMax: null,
                depth: null,
                hasProducts: false,
                hasDescription: false,
                hasImage: false,
                activeOnly: true,
                attributeGroup: null,
                featureGroup: null,
                dateAddFrom: null,
                dateAddTo: null,
                lastProductFrom: null,
                lastProductTo: null,
                // Country-specific filters
                hasHolidays: false,
                containsStates: false,
                zone: null
            };

            if (this.$dropdown) {
                var trans = this.config.trans || {};
                this.$dropdown.find('.refine-input').val('').attr('placeholder', trans.refine_short || 'Refine...');
                this.$dropdown.find('.btn-clear-refine').hide();
                this.$dropdown.find('.btn-refine-negate').removeClass('active');
                this.$dropdown.find('.filter-in-stock').prop('checked', false);
                this.$dropdown.find('.filter-discounted').prop('checked', false);
                this.$dropdown.find('.filter-price-min').val('');
                this.$dropdown.find('.filter-price-max').val('');
                this.$dropdown.find('.filter-attr-chip').removeClass('active');
                this.$dropdown.find('.filter-feat-chip').removeClass('active');
                this.$dropdown.find('.filter-group-toggle').removeClass('active has-selection');
                this.$dropdown.find('.filter-row-values').hide();

                // Clear entity-specific filter inputs
                this.$dropdown.find('.filter-product-count-min, .filter-product-count-max').val('');
                this.$dropdown.find('.filter-sales-min, .filter-sales-max').val('');
                this.$dropdown.find('.filter-turnover-min, .filter-turnover-max').val('');
                this.$dropdown.find('.filter-date-add-from, .filter-date-add-to').val('');
                this.$dropdown.find('.filter-last-product-from, .filter-last-product-to').val('');
                this.$dropdown.find('.filter-depth-select').val('');
                this.$dropdown.find('.filter-has-products').prop('checked', false);
                this.$dropdown.find('.filter-has-description').prop('checked', false);
                this.$dropdown.find('.filter-has-image').prop('checked', false);
                this.$dropdown.find('.filter-active-only').prop('checked', true);
                this.$dropdown.find('.filter-attribute-group-select, .filter-feature-group-select').val('');
                // Country filters
                this.$dropdown.find('.filter-has-holidays').prop('checked', false);
                this.$dropdown.find('.filter-contains-states').prop('checked', false);
                this.$dropdown.find('.filter-zone-select').val('');
            }

            this.refreshSearch();
        },

        // Reset filters without triggering a search (used when switching entity types)
        resetFiltersWithoutSearch: function() {
            this.refineQuery = '';
            this.refineNegate = false;
            this.filters = {
                inStock: false,
                discounted: false,
                priceMin: null,
                priceMax: null,
                attributes: [],
                features: [],
                productCountMin: null,
                productCountMax: null,
                salesMin: null,
                salesMax: null,
                turnoverMin: null,
                turnoverMax: null,
                depth: null,
                hasProducts: false,
                hasDescription: false,
                hasImage: false,
                activeOnly: true,
                attributeGroup: null,
                featureGroup: null,
                dateAddFrom: null,
                dateAddTo: null,
                lastProductFrom: null,
                lastProductTo: null,
                // Country-specific filters
                hasHolidays: false,
                containsStates: false,
                zone: null
            };

            if (this.$dropdown) {
                var trans = this.config.trans || {};
                this.$dropdown.find('.refine-input').val('').attr('placeholder', trans.refine_short || 'Refine...');
                this.$dropdown.find('.btn-clear-refine').hide();
                this.$dropdown.find('.btn-refine-negate').removeClass('active');
                this.$dropdown.find('.filter-in-stock').prop('checked', false);
                this.$dropdown.find('.filter-discounted').prop('checked', false);
                this.$dropdown.find('.filter-price-min').val('');
                this.$dropdown.find('.filter-price-max').val('');
                this.$dropdown.find('.filter-attr-chip').removeClass('active');
                this.$dropdown.find('.filter-feat-chip').removeClass('active');
                this.$dropdown.find('.filter-group-toggle').removeClass('active has-selection');
                this.$dropdown.find('.filter-row-values').hide();
                this.$dropdown.find('.filter-product-count-min, .filter-product-count-max').val('');
                this.$dropdown.find('.filter-sales-min, .filter-sales-max').val('');
                this.$dropdown.find('.filter-turnover-min, .filter-turnover-max').val('');
                this.$dropdown.find('.filter-date-add-from, .filter-date-add-to').val('');
                this.$dropdown.find('.filter-last-product-from, .filter-last-product-to').val('');
                this.$dropdown.find('.filter-depth-select').val('');
                this.$dropdown.find('.filter-has-products').prop('checked', false);
                this.$dropdown.find('.filter-has-description').prop('checked', false);
                this.$dropdown.find('.filter-has-image').prop('checked', false);
                this.$dropdown.find('.filter-active-only').prop('checked', true);
                this.$dropdown.find('.filter-attribute-group-select, .filter-feature-group-select').val('');
                // Country filters
                this.$dropdown.find('.filter-has-holidays').prop('checked', false);
                this.$dropdown.find('.filter-contains-states').prop('checked', false);
                this.$dropdown.find('.filter-zone-select').val('');
            }
            // Note: Does NOT call refreshSearch() - caller handles search/load
        },

        updateFilterPanelForEntity: function(entityType) {
            if (!this.$dropdown) {
                return;
            }

            var $panel = this.$dropdown.find('.filter-panel');

            // Hide all filter rows first
            $panel.find('.filter-row').hide();

            // Show/hide tree view option based on entity type
            var $treeOption = this.$dropdown.find('.view-mode-select option.tree-view-option');
            if (entityType === 'categories' || entityType === 'cms_categories') {
                $treeOption.prop('disabled', false).prop('hidden', false);
                // Auto-switch to tree view for categories
                if (this.viewMode !== 'tree') {
                    this.viewMode = 'tree';
                    this.$dropdown.find('.view-mode-select').val('tree');
                    this.$dropdown.removeClass('view-list view-cols-2 view-cols-3 view-cols-4 view-cols-5 view-cols-6 view-cols-7 view-cols-8').addClass('view-tree');
                    this.loadCategoryTree();
                } else {
                    this.loadCategoryTree();
                }
            } else {
                $treeOption.prop('disabled', true).prop('hidden', true);
                // If currently in tree mode, switch back to list
                if (this.viewMode === 'tree') {
                    this.viewMode = 'list';
                    this.$dropdown.find('.view-mode-select').val('list');
                    this.$dropdown.removeClass('view-tree').addClass('view-list');
                }
            }

            // Show entity-specific filter row (prepare visibility, but don't auto-expand panel)
            if (entityType === 'products') {
                // Prepare the correct rows to be visible when panel is shown
                $panel.find('.filter-row-quick').show();
                // Show attribute/feature rows if we have cached data
                if (this.filterableData) {
                    if (this.filterableData.attributes && this.filterableData.attributes.length > 0) {
                        this.$dropdown.find('.filter-row-attributes').show();
                    }
                    if (this.filterableData.features && this.filterableData.features.length > 0) {
                        this.$dropdown.find('.filter-row-features').show();
                    }
                }
            } else if (entityType === 'categories') {
                $panel.find('.filter-row-entity-categories').show();
            } else if (entityType === 'manufacturers') {
                $panel.find('.filter-row-entity-manufacturers').show();
            } else if (entityType === 'suppliers') {
                $panel.find('.filter-row-entity-suppliers').show();
            } else if (entityType === 'attributes') {
                $panel.find('.filter-row-entity-attributes').show();
                this.loadAttributeGroups();
            } else if (entityType === 'features') {
                $panel.find('.filter-row-entity-features').show();
            } else if (entityType === 'cms') {
                $panel.find('.filter-row-entity-cms').show();
            } else if (entityType === 'cms_categories') {
                $panel.find('.filter-row-entity-cms-categories').show();
            } else if (entityType === 'countries') {
                $panel.find('.filter-row-entity-countries').show();
                this.loadZonesForCountryFilter();
            }
        },

        loadAttributeGroups: function() {
            var self = this;
            var $select = this.$dropdown.find('.filter-attribute-group-select');

            // Already loaded?
            if ($select.find('option').length > 1) return;

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'getAttributeGroups',
                    trait: 'EntitySelector'
                },
                success: function(response) {
                    if (response.success && response.groups) {
                        $.each(response.groups, function(i, group) {
                            $select.append('<option value="' + group.id + '">' + self.escapeHtml(group.name) + ' (' + group.count + ')</option>');
                        });
                    }
                }
            });
        },

        loadFeatureGroups: function() {
            var self = this;
            var $select = this.$dropdown.find('.filter-feature-group-select');

            // Already loaded?
            if ($select.find('option').length > 1) return;

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'getFeatureGroups',
                    trait: 'EntitySelector'
                },
                success: function(response) {
                    if (response.success && response.groups) {
                        $.each(response.groups, function(i, group) {
                            $select.append('<option value="' + group.id + '">' + self.escapeHtml(group.name) + ' (' + group.count + ')</option>');
                        });
                    }
                }
            });
        }
    };

})(jQuery);

/**
 * Entity Selector - Filters Module
 * Filter panel, filter state management
 * @partial _filters.js
 *
 * EXTRACTION SOURCE: assets/js/admin/entity-selector.js
 * Lines: 6605-6758 (filter methods)
 *
 * Contains:
 * - clearFilters() - Reset all filters
 * - resetFiltersWithoutSearch() - Reset without triggering search
 * - updateFilterPanelForEntity() - Show/hide filters based on entity type
 * - loadFilterableData() - Load attributes/features for filter panel
 * - renderFilterDropdowns() - Render attribute/feature group toggles
 * - showFilterGroupValues() - Show values for a filter group
 * - hideFilterGroupValues() - Hide filter values row
 * - updateFilterToggleStates() - Update toggle states based on selections
 */

(function($) {
    'use strict';

    window._EntitySelectorMixins = window._EntitySelectorMixins || {};

    window._EntitySelectorMixins.filters = {

        clearFilters: function() {
            this.refineQuery = '';
            this.refineNegate = false;
            this.filters = {
                inStock: false,
                discounted: false,
                priceMin: null,
                priceMax: null,
                attributes: [],
                features: [],
                productCountMin: null,
                productCountMax: null,
                salesMin: null,
                salesMax: null,
                turnoverMin: null,
                turnoverMax: null,
                depth: null,
                hasProducts: false,
                hasDescription: false,
                hasImage: false,
                activeOnly: true,
                attributeGroup: null,
                featureGroup: null,
                dateAddFrom: null,
                dateAddTo: null,
                lastProductFrom: null,
                lastProductTo: null,
                // Country-specific filters
                hasHolidays: false,
                containsStates: false,
                zone: null
            };

            if (this.$dropdown) {
                var trans = this.config.trans || {};
                this.$dropdown.find('.refine-input').val('');
                this.$dropdown.find('.btn-refine-negate').removeClass('active');
                this.$dropdown.find('.filter-in-stock').prop('checked', false);
                this.$dropdown.find('.filter-discounted').prop('checked', false);
                this.$dropdown.find('.filter-price-min, .filter-price-max').val('');
                this.$dropdown.find('.filter-attr-chip, .filter-feat-chip').removeClass('active');
                this.$dropdown.find('.filter-product-count-min, .filter-product-count-max').val('');
                this.$dropdown.find('.filter-sales-min, .filter-sales-max').val('');
                this.$dropdown.find('.filter-depth-select').val('');
                this.$dropdown.find('.filter-has-products').prop('checked', false);
                this.$dropdown.find('.filter-active-only').prop('checked', true);
                // Country filters
                this.$dropdown.find('.filter-has-holidays').prop('checked', false);
                this.$dropdown.find('.filter-contains-states').prop('checked', false);
                this.$dropdown.find('.filter-zone-select').val('');
            }

            this.refreshSearch();
        },

        resetFiltersWithoutSearch: function() {
            // Same as clearFilters but doesn't trigger search
            // Used when switching entity types
            this.refineQuery = '';
            this.refineNegate = false;
            this.filters = {
                inStock: false,
                discounted: false,
                priceMin: null,
                priceMax: null,
                attributes: [],
                features: [],
                productCountMin: null,
                productCountMax: null,
                salesMin: null,
                salesMax: null,
                turnoverMin: null,
                turnoverMax: null,
                depth: null,
                hasProducts: false,
                hasDescription: false,
                hasImage: false,
                activeOnly: true,
                attributeGroup: null,
                featureGroup: null,
                dateAddFrom: null,
                dateAddTo: null,
                lastProductFrom: null,
                lastProductTo: null,
                // Country-specific filters
                hasHolidays: false,
                containsStates: false,
                zone: null
            };
        },

        updateFilterPanelForEntity: function(entityType) {
            if (!this.$dropdown) {
                return;
            }

            var $panel = this.$dropdown.find('.filter-panel');

            // Hide all entity-specific filter rows
            $panel.find('.filter-row').hide();

            // Show filters for current entity type
            $panel.find('.filter-row[data-entity="' + entityType + '"]').show();
            $panel.find('.filter-row-entity-' + entityType.replace('_', '-')).show();

            // Show/hide tree view option based on entity type
            var isCategory = (entityType === 'categories' || entityType === 'cms_categories');
            this.$dropdown.find('.tree-view-option').toggle(isCategory);

            // Default to tree view for categories (only if currently on list mode)
            if (isCategory && this.viewMode === 'list') {
                this.viewMode = 'tree';
                this.$dropdown.find('.view-mode-select').val('tree');
                this.$dropdown.removeClass('view-list view-cols-2 view-cols-3 view-cols-4 view-cols-5 view-cols-6 view-cols-7 view-cols-8').addClass('view-tree');
            } else if (!isCategory && this.viewMode === 'tree') {
                // If switching away from categories while in tree mode, switch to list
                this.viewMode = 'list';
                this.$dropdown.find('.view-mode-select').val('list');
                this.$dropdown.removeClass('view-tree view-cols-2 view-cols-3 view-cols-4 view-cols-5 view-cols-6 view-cols-7 view-cols-8').addClass('view-list');
            }

            // Load zones for countries filter
            if (entityType === 'countries') {
                this.loadZonesForCountryFilter();
            }

            // Update sort options for entity type
            this.updateSortOptionsForEntity(entityType);
        },

        /**
         * Show/hide sort options based on entity type
         * Options with data-entities attribute are only shown for matching entities
         */
        updateSortOptionsForEntity: function(entityType) {
            if (!this.$dropdown) {
                return;
            }

            var $select = this.$dropdown.find('.sort-field-select');
            var currentValue = $select.val();
            var hasCurrentOption = false;

            $select.find('option').each(function() {
                var $option = $(this);
                var entities = $option.data('entities');

                // Options without data-entities are universal (always shown)
                if (!entities) {
                    $option.show();
                    if ($option.val() === currentValue) {
                        hasCurrentOption = true;
                    }
                    return;
                }

                // Check if this entity type is in the allowed list
                var allowedEntities = entities.split(',');
                var isAllowed = allowedEntities.indexOf(entityType) !== -1;

                $option.toggle(isAllowed);

                if (isAllowed && $option.val() === currentValue) {
                    hasCurrentOption = true;
                }
            });

            // If current sort field is not available for this entity, reset to 'name'
            if (!hasCurrentOption) {
                $select.val('name');
                this.currentSort.field = 'name';
            }
        },

        loadFilterableData: function() {
            var self = this;

            if (this.filterableData) {
                this.renderFilterDropdowns();
                return;
            }

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                data: {
                    ajax: 1,
                    action: 'getTargetFilterableAttributes',
                    trait: 'EntitySelector'
                },
                dataType: 'json',
                success: function(response) {
                    if (response.success && response.data) {
                        self.filterableData = response.data;
                        self.renderFilterDropdowns();
                    }
                }
            });
        },

        renderFilterDropdowns: function() {
            if (!this.$dropdown || !this.filterableData) return;

            var self = this;

            // Render attribute group toggle buttons
            var $attrContainer = this.$dropdown.find('.filter-attributes-container');
            $attrContainer.empty();

            if (this.filterableData.attributes && this.filterableData.attributes.length > 0) {
                this.filterableData.attributes.forEach(function(group) {
                    var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '">';
                    html += '<span class="toggle-name">' + group.name + '</span>';
                    if (group.count !== undefined) {
                        html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="attribute" data-group-name="' + self.escapeAttr(group.name) + '">' + self.esIcon('visibility') + ' ' + group.count + '</span>';
                    }
                    html += '</button>';
                    $attrContainer.append(html);
                });
                this.$dropdown.find('.filter-row-attributes').show();
            }

            // Render feature group toggle buttons
            var $featContainer = this.$dropdown.find('.filter-features-container');
            $featContainer.empty();

            if (this.filterableData.features && this.filterableData.features.length > 0) {
                this.filterableData.features.forEach(function(group) {
                    var html = '<button type="button" class="filter-group-toggle" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '">';
                    html += '<span class="toggle-name">' + group.name + '</span>';
                    if (group.count !== undefined) {
                        html += '<span class="toggle-count clickable" data-group-id="' + group.id + '" data-type="feature" data-group-name="' + self.escapeAttr(group.name) + '">' + self.esIcon('visibility') + ' ' + group.count + '</span>';
                    }
                    html += '</button>';
                    $featContainer.append(html);
                });
                this.$dropdown.find('.filter-row-features').show();
            }
        },

        showFilterGroupValues: function(groupId, type) {
            if (!this.filterableData) return;

            var self = this;
            var groups = type === 'attribute' ? this.filterableData.attributes : this.filterableData.features;
            var group = groups.find(function(g) { return g.id == groupId; });

            if (!group) return;

            // Hide all values rows first, then show the correct one
            this.$dropdown.find('.filter-row-values').hide();

            // Target the correct values row based on type
            var valuesRowClass = type === 'attribute' ? '.filter-row-attr-values' : '.filter-row-feat-values';
            var $filterRowValues = this.$dropdown.find(valuesRowClass);
            var $valuesContainer = $filterRowValues.find('.filter-values-container');
            $valuesContainer.empty();

            // Add group label
            var html = '<span class="filter-values-label">' + group.name + ':</span>';

            // Add chips
            group.values.forEach(function(val) {
                var isActive = type === 'attribute'
                    ? self.filters.attributes.indexOf(val.id) !== -1
                    : self.filters.features.indexOf(val.id) !== -1;
                var activeClass = isActive ? ' active' : '';
                var chipClass = type === 'attribute' ? 'filter-attr-chip' : 'filter-feat-chip';
                var colorStyle = val.color ? ' style="--chip-color: ' + val.color + '"' : '';
                var colorClass = val.color ? ' has-color' : '';

                html += '<button type="button" class="filter-chip ' + chipClass + activeClass + colorClass + '" data-id="' + val.id + '" data-group-id="' + groupId + '"' + colorStyle + '>';
                if (val.color) {
                    html += '<span class="chip-color-dot"></span>';
                }
                html += '<span class="chip-name">' + val.name + '</span>';
                if (val.count !== undefined) {
                    html += '<span class="chip-count">(' + val.count + ')</span>';
                }
                html += '</button>';
            });

            $valuesContainer.html(html);

            // Add close button as sibling (outside filter-values-container, inside filter-row-values)
            $filterRowValues.find('.btn-close-values').remove();
            $filterRowValues.append('<button type="button" class="btn-close-values">' + this.esIcon('close') + '</button>');
            $filterRowValues.show();

            // Scroll into view if needed
            var rowValues = $filterRowValues[0];
            if (rowValues) {
                rowValues.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
            }
        },

        hideFilterGroupValues: function() {
            this.$dropdown.find('.filter-row-values').hide();
            this.$dropdown.find('.filter-group-toggle').removeClass('active');
        },

        updateFilterToggleStates: function() {
            if (!this.$dropdown || !this.filterableData) return;

            var self = this;

            // Update attribute group toggles
            if (this.filterableData.attributes) {
                this.filterableData.attributes.forEach(function(group) {
                    var $toggle = self.$dropdown.find('.filter-group-toggle[data-group-id="' + group.id + '"][data-type="attribute"]');
                    var hasActiveInGroup = group.values.some(function(val) {
                        return self.filters.attributes.indexOf(val.id) !== -1;
                    });
                    $toggle.toggleClass('has-selection', hasActiveInGroup);
                });
            }

            // Update feature group toggles
            if (this.filterableData.features) {
                this.filterableData.features.forEach(function(group) {
                    var $toggle = self.$dropdown.find('.filter-group-toggle[data-group-id="' + group.id + '"][data-type="feature"]');
                    var hasActiveInGroup = group.values.some(function(val) {
                        return self.filters.features.indexOf(val.id) !== -1;
                    });
                    $toggle.toggleClass('has-selection', hasActiveInGroup);
                });
            }
        },

        /**
         * Load zones for country filter dropdown
         */
        loadZonesForCountryFilter: function() {
            var self = this;

            if (this.zonesLoaded || !this.$dropdown) {
                return;
            }

            var $select = this.$dropdown.find('.filter-zone-select');
            if (!$select.length) {
                return;
            }

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'getZonesForFilter',
                    trait: 'EntitySelector'
                },
                success: function(response) {
                    if (response.success && response.zones && response.zones.length > 0) {
                        var trans = self.config.trans || {};
                        $select.empty();
                        $select.append('<option value="">' + (trans.all_zones || 'All zones') + '</option>');

                        response.zones.forEach(function(zone) {
                            $select.append('<option value="' + zone.id + '">' + self.escapeHtml(zone.name) + '</option>');
                        });

                        self.zonesLoaded = true;
                    }
                }
            });
        }
    };

})(jQuery);

/**
 * Entity Selector - Chips Module
 * Entity chip rendering, selection management, and pattern tag handling
 * @partial _chips.js
 *
 * EXTRACTION SOURCE: assets/js/admin/entity-selector.js
 *
 * Contains:
 * - addSelection() / addSelectionNoUpdate() - Add entity chip to picker
 * - removeSelection() - Remove chip and update state
 * - updateChipsVisibility() - Show/hide based on count
 * - loadExistingSelections() - Load saved values on init
 * - collectPickerEntities() / loadPickerValues() - Entity loading helpers
 * - Pattern tag methods: addPatternTag, getPatternTags, updateDraftTagCount
 * - Single mode: getCurrentSingleSelection, showReplaceConfirmation
 * - Count updates: updateConditionCount, updateGroupCounts, updateGroupTotalCount
 */

(function($) {
    'use strict';

    window._EntitySelectorMixins = window._EntitySelectorMixins || {};

    window._EntitySelectorMixins.chips = {

        // =========================================================================
        // Selection Methods (Entity Chips)
        // =========================================================================

        addSelection: function($picker, id, name, data) {
            this.addSelectionNoUpdate($picker, id, name, data);
            var $chips = $picker.find('.entity-chips');
            this.updateChipsVisibility($chips);
        },

        addSelectionNoUpdate: function($picker, id, name, data) {
            var $chips = $picker.find('.entity-chips');
            var $block = $picker.closest('.target-block');

            // Check for global single mode (only ONE item across ALL entity types)
            var globalMode = this.config.mode || 'multi';

            if (globalMode === 'single') {
                // Clear ALL selections in ALL blocks (across all entity types)
                this.$wrapper.find('.entity-chips .entity-chip').remove();
                // Clear all selected states in dropdown
                if (this.$dropdown) {
                    this.$dropdown.find('.dropdown-item.selected, .tree-item.selected').removeClass('selected');
                }
                // Clear tab badges (since we're clearing other blocks)
                this.$wrapper.find('.target-block-tab .tab-badge').remove();
                this.$wrapper.find('.target-block-tab').removeClass('has-data');
            } else {
                // Check if this block is in per-block single mode
                var blockMode = $block.data('mode') || 'multi';

                // In per-block single mode, clear chips in THIS block only
                if (blockMode === 'single') {
                    $chips.find('.entity-chip').remove();
                    // Also deselect all items in dropdown
                    if (this.$dropdown) {
                        this.$dropdown.find('.dropdown-item.selected, .tree-item.selected').removeClass('selected');
                    }
                }
            }

            if ($chips.find('.entity-chip[data-id="' + id + '"]').length) {
                return;
            }

            // Check if this is a country entity (for flag and holiday preview)
            var blockType = $block.data('blockType') || '';
            var searchEntity = $picker.attr('data-search-entity') || blockType;
            var isCountry = (searchEntity === 'countries');

            var html = '<span class="entity-chip" data-id="' + this.escapeAttr(id) + '"';
            if (isCountry && data && data.iso_code) {
                html += ' data-iso="' + this.escapeAttr(data.iso_code) + '"';
            }
            html += '>';

            // Country: show flag
            if (isCountry && data && data.iso_code) {
                html += '<span class="chip-flag"><img src="https://flagcdn.com/16x12/' + this.escapeAttr(data.iso_code.toLowerCase()) + '.png" alt="' + this.escapeAttr(data.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'inline-flex\';">' + this.esIcon('flag', 'flag-fallback').replace('>', ' style="display:none">') + '</span>';
            } else if (data && data.image) {
                html += '<span class="chip-icon"><img src="' + this.escapeAttr(data.image) + '" alt=""></span>';
            }

            html += '<span class="chip-name">' + this.escapeHtml(name) + '</span>';

            // Country: add holiday preview button
            if (isCountry) {
                html += '<button type="button" class="chip-preview-holidays" title="Preview holidays">' + this.esIcon('visibility') + '</button>';
            }

            html += '<button type="button" class="chip-remove" title="Remove">' + this.esIcon('close') + '</button>';
            html += '</span>';

            $chips.append(html);
        },

        removeSelection: function($picker, id) {
            var $chips = $picker.find('.entity-chips');
            $picker.find('.entity-chip[data-id="' + id + '"]').remove();
            this.updateChipsVisibility($chips);
        },

        updateChipsVisibility: function($chips) {
            var self = this;
            var trans = this.config.trans || {};
            var $picker = $chips.closest('.value-picker');
            var $allChips = $chips.find('.entity-chip');
            var totalCount = $allChips.length;

            // If no chips, remove the wrapper entirely
            var $existingWrapper = $chips.closest('.chips-wrapper');
            if (totalCount === 0) {
                if ($existingWrapper.length) {
                    // Move chips out of wrapper before removing
                    $existingWrapper.before($chips);
                    $existingWrapper.remove();
                }
                return;
            }

            // Ensure chips wrapper structure exists
            this.ensureChipsWrapper($chips);

            var $wrapper = $chips.closest('.chips-wrapper');
            var $toolbar = $wrapper.find('.chips-toolbar');
            var $loadMore = $wrapper.find('.chips-load-more');

            // Get current search filter
            var searchTerm = $toolbar.find('.chips-search-input').val() || '';
            searchTerm = searchTerm.toLowerCase().trim();

            // Filter and paginate chips
            var visibleCount = 0;
            var filteredCount = 0;
            var isExpanded = $chips.hasClass('chips-expanded');
            var maxVisible = isExpanded ? 999999 : (this.maxVisibleChips || 12);

            $allChips.each(function() {
                var $chip = $(this);
                var chipName = ($chip.find('.chip-name').text() || '').toLowerCase();
                var matchesFilter = !searchTerm || chipName.indexOf(searchTerm) !== -1;

                $chip.removeClass('chip-filtered-out chip-paginated-out');

                if (!matchesFilter) {
                    $chip.addClass('chip-filtered-out');
                } else {
                    filteredCount++;
                    if (filteredCount > maxVisible) {
                        $chip.addClass('chip-paginated-out');
                    } else {
                        visibleCount++;
                    }
                }
            });

            // Update toolbar (always show when we have chips)
            $toolbar.addClass('has-chips');
            this.updateChipsToolbar($toolbar, totalCount, filteredCount, searchTerm);

            // Update load more select dropdown
            var hiddenByPagination = filteredCount - visibleCount;
            if (hiddenByPagination > 0 && !isExpanded) {
                var loadText = trans.load || 'Load';
                var remainingText = (trans.remaining || '{count} remaining').replace('{count}', hiddenByPagination);
                var loadMoreHtml = '<span class="load-more-label">' + loadText + '</span>' +
                    '<select class="load-more-select">' +
                    '<option value="20">20</option>' +
                    '<option value="50">50</option>' +
                    '<option value="100">100</option>' +
                    '<option value="all">' + (trans.all || 'All') + ' (' + hiddenByPagination + ')</option>' +
                    '</select>' +
                    '<span class="load-more-remaining">' + remainingText + '</span>';
                $loadMore.html(loadMoreHtml).show();
            } else if (isExpanded && filteredCount > (this.maxVisibleChips || 12)) {
                var collapseText = trans.collapse || 'Collapse';
                $loadMore.html(
                    '<button type="button" class="btn-collapse-chips" style="' +
                    'background:transparent;border:1px solid #dee2e6;border-radius:4px;' +
                    'padding:0.25rem 0.75rem;font-size:12px;color:#6c757d;cursor:pointer;">' +
                    this.esIcon('expand_less') + ' ' + collapseText +
                    '</button>'
                ).show();
            } else {
                $loadMore.hide();
            }
        },

        ensureChipsWrapper: function($chips) {
            // Check if already wrapped
            if ($chips.closest('.chips-wrapper').length) {
                return;
            }

            var trans = this.config.trans || {};
            var $picker = $chips.closest('.value-picker');

            // Create wrapper structure - integrated filter toolbar with sort
            var wrapperHtml = '<div class="chips-wrapper">' +
                '<div class="chips-toolbar">' +
                '<input type="text" class="chips-search-input" placeholder="' + (trans.filter_selected || 'Filter selected') + '...">' +
                '<select class="chips-sort-select" title="' + (trans.sort || 'Sort') + '">' +
                '<option value="added">' + (trans.sort_added || 'Order added') + '</option>' +
                '<option value="name_asc">' + (trans.sort_name_asc || 'Name A-Z') + '</option>' +
                '<option value="name_desc">' + (trans.sort_name_desc || 'Name Z-A') + '</option>' +
                '</select>' +
                '<span class="chips-count"></span>' +
                '<button type="button" class="btn-chips-clear" title="' + (trans.clear_all || 'Clear all') + '">' +
                this.esIcon('delete') + ' <span class="clear-text">' + (trans.clear || 'Clear') + '</span>' +
                '</button>' +
                '</div>' +
                '<div class="chips-load-more" style="display:none;"></div>' +
                '</div>';

            var $wrapper = $(wrapperHtml);

            // Insert wrapper before chips and move chips inside
            $chips.before($wrapper);
            $wrapper.find('.chips-toolbar').after($chips);
            $wrapper.append($wrapper.find('.chips-load-more'));

            // Bind toolbar events
            this.bindChipsToolbarEvents($wrapper);
        },

        bindChipsToolbarEvents: function($wrapper) {
            var self = this;
            var $chips = $wrapper.find('.entity-chips');
            var searchTimeout;

            // Search input
            $wrapper.on('input', '.chips-search-input', function() {
                clearTimeout(searchTimeout);
                searchTimeout = setTimeout(function() {
                    // Collapse when searching to show filtered results from start
                    $chips.removeClass('chips-expanded');
                    self.updateChipsVisibility($chips);
                }, 150);
            });

            // Sort select
            $wrapper.on('change', '.chips-sort-select', function() {
                var sortBy = $(this).val();
                self.sortChips($chips, sortBy);
            });

            // Clear all button
            $wrapper.on('click', '.btn-chips-clear', function() {
                var searchTerm = $wrapper.find('.chips-search-input').val() || '';
                var $chipsToRemove;

                if (searchTerm.trim()) {
                    // Remove only filtered (visible) chips
                    $chipsToRemove = $chips.find('.entity-chip:not(.chip-filtered-out)');
                } else {
                    // Remove all chips
                    $chipsToRemove = $chips.find('.entity-chip');
                }

                $chipsToRemove.each(function() {
                    $(this).find('.chip-remove').trigger('click');
                });

                // Clear search
                $wrapper.find('.chips-search-input').val('');
                self.updateChipsVisibility($chips);
            });

            // Load more select dropdown
            $wrapper.on('change', '.load-more-select', function() {
                var loadCount = $(this).val();
                if (loadCount === 'all') {
                    $chips.addClass('chips-expanded');
                    self.maxVisibleChips = 999999;
                } else {
                    self.maxVisibleChips = (self.maxVisibleChips || 12) + parseInt(loadCount, 10);
                }
                self.updateChipsVisibility($chips);
            });

            // Collapse button
            $wrapper.on('click', '.btn-collapse-chips', function() {
                $chips.removeClass('chips-expanded');
                self.maxVisibleChips = 12;
                self.updateChipsVisibility($chips);
            });
        },

        /**
         * Sort chips by specified criteria
         */
        sortChips: function($chips, sortBy) {
            var $allChips = $chips.find('.entity-chip');
            if ($allChips.length < 2) return;

            var sorted = $allChips.toArray().sort(function(a, b) {
                var $a = $(a);
                var $b = $(b);

                switch (sortBy) {
                    case 'name_asc':
                        var nameA = ($a.find('.chip-name').text() || '').toLowerCase();
                        var nameB = ($b.find('.chip-name').text() || '').toLowerCase();
                        return nameA.localeCompare(nameB);
                    case 'name_desc':
                        var nameA2 = ($a.find('.chip-name').text() || '').toLowerCase();
                        var nameB2 = ($b.find('.chip-name').text() || '').toLowerCase();
                        return nameB2.localeCompare(nameA2);
                    case 'added':
                    default:
                        // Keep original DOM order (order added)
                        return 0;
                }
            });

            // Re-append in sorted order
            $.each(sorted, function(i, chip) {
                $chips.append(chip);
            });

            this.updateChipsVisibility($chips);
        },

        updateChipsToolbar: function($toolbar, totalCount, filteredCount, searchTerm) {
            var trans = this.config.trans || {};
            var $count = $toolbar.find('.chips-count');
            var $clearBtn = $toolbar.find('.btn-chips-clear');
            var $clearText = $clearBtn.find('.clear-text');

            // Update count display
            if (searchTerm) {
                $count.addClass('has-filter').html(
                    '<span class="count-filtered">' + filteredCount + '</span>' +
                    '<span class="count-separator">/</span>' +
                    '<span class="count-total">' + totalCount + '</span>'
                );
                $clearText.text((trans.clear || 'Clear') + ' ' + filteredCount);
            } else {
                $count.removeClass('has-filter').html(totalCount);
                $clearText.text(trans.clear_all || 'Clear all');
            }

            // Show/hide clear button
            if (searchTerm && filteredCount === 0) {
                $clearBtn.hide();
            } else if (totalCount > 0) {
                $clearBtn.show();
            } else {
                $clearBtn.hide();
            }
        },

        // =========================================================================
        // Loading/Initialization
        // =========================================================================

        loadExistingSelections: function() {
            var self = this;
            console.log('[EntitySelector] loadExistingSelections called for id:', this.config.id);

            // Collect all entity IDs to load, grouped by entity type
            var entitiesToLoad = {}; // { entity_type: { ids: [], pickers: [] } }

            console.log('[EntitySelector] Looking for .selection-group in wrapper:', this.$wrapper.length ? 'found' : 'NOT FOUND');
            this.$wrapper.find('.selection-group').each(function() {
                console.log('[EntitySelector] Found .selection-group, index:', $(this).data('groupIndex'));
                var $group = $(this);
                var $block = $group.closest('.target-block');
                var blockType = $block.data('blockType');

                // Load include values
                var $includePicker = $group.find('.include-picker');
                self.collectPickerEntities($includePicker, blockType, entitiesToLoad);

                // Enhance the include method select if not already enhanced
                self.enhanceMethodSelect($group.find('.include-method-select'));

                // Load exclude values from each exclude row
                $group.find('.exclude-row').each(function() {
                    var $excludeRow = $(this);
                    self.collectPickerEntities($excludeRow.find('.exclude-picker'), blockType, entitiesToLoad);

                    // Enhance the exclude method select if not already enhanced
                    self.enhanceMethodSelect($excludeRow.find('.exclude-method-select'));
                });

                // Lock method selector if excludes exist
                var hasExcludes = $group.find('.group-excludes.has-excludes').length > 0;
                if (hasExcludes) {
                    self.updateMethodSelectorLock($group, true);
                }
            });

            // Build bulk request: { entityType: [uniqueIds], ... }
            var bulkRequest = {};
            var hasEntities = false;

            Object.keys(entitiesToLoad).forEach(function(entityType) {
                var data = entitiesToLoad[entityType];
                if (data.ids.length === 0) return;

                // Deduplicate IDs
                var uniqueIds = data.ids.filter(function(id, index, arr) {
                    return arr.indexOf(id) === index;
                });

                bulkRequest[entityType] = uniqueIds;
                hasEntities = true;
            });

            // Skip AJAX if no entities to load
            if (!hasEntities) {
                console.log('[EntitySelector] No entities to load, skipping AJAX');
                return;
            }

            console.log('[EntitySelector] Making bulk AJAX request for entities:', JSON.stringify(bulkRequest));

            // Single bulk AJAX call for all entity types
            $.ajax({
                url: self.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'getTargetEntitiesByIdsBulk',
                    trait: 'EntitySelector',
                    entities: JSON.stringify(bulkRequest)
                },
                success: function(response) {
                    console.log('[EntitySelector] AJAX response:', response);
                    if (!response.success || !response.entities) {
                        console.log('[EntitySelector] Response failed or no entities');
                        return;
                    }
                    try {

                    // Process each entity type's results
                    Object.keys(entitiesToLoad).forEach(function(entityType) {
                        var data = entitiesToLoad[entityType];
                        var entities = response.entities[entityType] || [];

                        // Build a map of id -> entity for quick lookup
                        var entityMap = {};
                        entities.forEach(function(entity) {
                            entityMap[entity.id] = entity;
                        });

                        // Update each picker that requested this entity type
                        data.pickers.forEach(function(pickerData) {
                            var $picker = pickerData.$picker;
                            var $chips = $picker.find('.entity-chips');
                            var $dataInput = $picker.find('.include-values-data, .exclude-values-data');
                            var validIds = [];

                            // Check if this is a country entity
                            var isCountry = (entityType === 'countries');

                            // Replace loading chips with real data
                            pickerData.ids.forEach(function(id) {
                                var $loadingChip = $chips.find('.entity-chip-loading[data-id="' + id + '"]');
                                if (entityMap[id]) {
                                    var entity = entityMap[id];
                                    validIds.push(entity.id);

                                    // Create real chip
                                    var html = '<span class="entity-chip" data-id="' + self.escapeAttr(entity.id) + '"';
                                    if (isCountry && entity.iso_code) {
                                        html += ' data-iso="' + self.escapeAttr(entity.iso_code) + '"';
                                    }
                                    html += '>';

                                    // Country: show flag
                                    if (isCountry && entity.iso_code) {
                                        html += '<span class="chip-flag"><img src="https://flagcdn.com/16x12/' + self.escapeAttr(entity.iso_code.toLowerCase()) + '.png" alt="' + self.escapeAttr(entity.iso_code) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'inline-flex\';">' + self.esIcon('flag', 'flag-fallback').replace('>', ' style="display:none">') + '</span>';
                                    } else if (entity.image) {
                                        html += '<span class="chip-icon"><img src="' + self.escapeAttr(entity.image) + '" alt=""></span>';
                                    }

                                    html += '<span class="chip-name">' + self.escapeHtml(entity.name) + '</span>';

                                    // Country: add holiday preview button
                                    if (isCountry) {
                                        html += '<button type="button" class="chip-preview-holidays" title="Preview holidays">' + self.esIcon('visibility') + '</button>';
                                    }

                                    html += '<button type="button" class="chip-remove" title="Remove">' + self.esIcon('close') + '</button>';
                                    html += '</span>';

                                    $loadingChip.replaceWith(html);
                                } else {
                                    // Entity not found, remove loading chip
                                    $loadingChip.remove();
                                }
                            });

                            // Update chips visibility
                            self.updateChipsVisibility($chips);

                            // If some entities were not found, update the hidden input
                            if (validIds.length !== pickerData.ids.length) {
                                $dataInput.val(JSON.stringify(validIds));
                                self.serializeAllBlocks();
                            }

                            self.updateBlockStatus($picker.closest('.target-block'));
                        });
                    });

                    // Update condition counts after chips are loaded (for holiday counts, etc.)
                    self.updateAllConditionCounts();

                    } catch (e) {
                        console.error('[EntitySelector] Error processing AJAX response:', e);
                    }
                },
                error: function(xhr, status, error) {
                    console.error('[EntitySelector] AJAX request failed:', status, error, xhr.responseText);
                }
            });
        },

        /**
         * Collect entity IDs from a picker for bulk loading
         * Also shows loading placeholders for entity_search types
         */
        collectPickerEntities: function($picker, blockType, entitiesToLoad) {
            console.log('[EntitySelector] collectPickerEntities called, blockType:', blockType, 'picker length:', $picker.length);
            if (!$picker.length) {
                console.log('[EntitySelector] Picker not found, returning');
                return;
            }

            var self = this;
            var $dataInput = $picker.find('.include-values-data, .exclude-values-data');
            console.log('[EntitySelector] Looking for values-data input, found:', $dataInput.length);
            if (!$dataInput.length) {
                console.log('[EntitySelector] No data input found, returning');
                return;
            }

            var valueType = $picker.attr('data-value-type');
            var rawValue = $dataInput.val() || '[]';
            console.log('[EntitySelector] valueType:', valueType, 'rawValue:', rawValue);

            var values = [];
            try {
                values = JSON.parse(rawValue);
                console.log('[EntitySelector] Parsed values:', values);
            } catch (e) {
                console.log('[EntitySelector] JSON parse error:', e);
                return;
            }

            // Handle non-entity types synchronously
            if (valueType === 'multi_numeric_range') {
                if (!Array.isArray(values) || values.length === 0) return;

                var $chipsContainer = $picker.find('.multi-range-chips');
                values.forEach(function(range) {
                    if (!range || (range.min === null && range.max === null)) return;

                    var chipText = '';
                    if (range.min !== null && range.max !== null) {
                        chipText = range.min + ' - ' + range.max;
                    } else if (range.min !== null) {
                        chipText = '≥ ' + range.min;
                    } else {
                        chipText = '≤ ' + range.max;
                    }

                    var $chip = $('<span>', {
                        class: 'range-chip',
                        'data-min': range.min !== null ? range.min : '',
                        'data-max': range.max !== null ? range.max : ''
                    });
                    $chip.append($('<span>', { class: 'range-chip-text', text: chipText }));
                    $chip.append($('<button>', {
                        type: 'button',
                        class: 'btn-remove-range',
                        html: self.esIcon('close')
                    }));

                    $chipsContainer.append($chip);
                });
                return;
            }

            if (valueType === 'multi_select_tiles') {
                if (!Array.isArray(values) || values.length === 0) return;

                values.forEach(function(key) {
                    $picker.find('.tile-option[data-value="' + key + '"]').addClass('selected');
                });
                return;
            }

            if (valueType === 'combination_attributes') {
                if (typeof values !== 'object' || values === null || Object.keys(values).length === 0) {
                    self.loadCombinationAttributeGroups($picker);
                    return;
                }

                $dataInput.val(JSON.stringify(values));
                self.loadCombinationAttributeGroups($picker);
                return;
            }

            if (!values.length) {
                return;
            }

            if (valueType === 'pattern') {
                values.forEach(function(item) {
                    if (typeof item === 'string' && item) {
                        self.addPatternTag($picker, item, false);
                    } else if (item && item.pattern) {
                        self.addPatternTag($picker, item.pattern, item.caseSensitive === true);
                    }
                });
                return;
            }

            // For entity_search type - show loading placeholders and collect for bulk load
            var searchEntity = $picker.attr('data-search-entity') || blockType;
            var $chips = $picker.find('.entity-chips');

            // Get icon for entity type
            var entityIcon = this.getEntityTypeIcon(searchEntity);

            // Show loading placeholders with entity-specific icons
            values.forEach(function(id) {
                var html = '<span class="entity-chip entity-chip-loading" data-id="' + self.escapeAttr(id) + '">';
                html += '<span class="chip-icon">' + self.esIcon(entityIcon, 'es-spin-pulse') + '</span>';
                html += '<span class="chip-name">Loading...</span>';
                html += '</span>';
                $chips.append(html);
            });

            // Collect for bulk load
            if (!entitiesToLoad[searchEntity]) {
                entitiesToLoad[searchEntity] = { ids: [], pickers: [] };
            }
            entitiesToLoad[searchEntity].ids = entitiesToLoad[searchEntity].ids.concat(values);
            entitiesToLoad[searchEntity].pickers.push({
                $picker: $picker,
                ids: values
            });
        },

        loadPickerValues: function($picker, blockType) {
            // This function is now only used for dynamic loading (not initial load)
            // Initial load uses collectPickerEntities + bulk AJAX
            if (!$picker.length) return;

            var self = this;
            var $dataInput = $picker.find('.include-values-data, .exclude-values-data');
            if (!$dataInput.length) return;

            var valueType = $picker.attr('data-value-type');
            var values = [];
            try {
                values = JSON.parse($dataInput.val() || '[]');
            } catch (e) {
                return;
            }

            // Handle empty/missing values based on type
            if (valueType === 'multi_numeric_range') {
                // For multi_numeric_range, values is an array of {min, max} objects
                if (!Array.isArray(values) || values.length === 0) return;

                var $chipsContainer = $picker.find('.multi-range-chips');
                values.forEach(function(range) {
                    if (!range || (range.min === null && range.max === null)) return;

                    var chipText = '';
                    if (range.min !== null && range.max !== null) {
                        chipText = range.min + ' - ' + range.max;
                    } else if (range.min !== null) {
                        chipText = '≥ ' + range.min;
                    } else {
                        chipText = '≤ ' + range.max;
                    }

                    var $chip = $('<span>', {
                        class: 'range-chip',
                        'data-min': range.min !== null ? range.min : '',
                        'data-max': range.max !== null ? range.max : ''
                    });
                    $chip.append($('<span>', { class: 'range-chip-text', text: chipText }));
                    $chip.append($('<button>', {
                        type: 'button',
                        class: 'btn-remove-range',
                        html: self.esIcon('close')
                    }));

                    $chipsContainer.append($chip);
                });
                return;
            }

            if (valueType === 'multi_select_tiles') {
                // For multi_select_tiles, values is an array of selected keys
                if (!Array.isArray(values) || values.length === 0) return;

                values.forEach(function(key) {
                    $picker.find('.tile-option[data-value="' + key + '"]').addClass('selected');
                });
                return;
            }

            if (valueType === 'combination_attributes') {
                // For combination_attributes, values is an object: { groupId: [valueId1, valueId2], ... }
                if (typeof values !== 'object' || values === null || Object.keys(values).length === 0) {
                    // Still need to load the attribute groups UI
                    self.loadCombinationAttributeGroups($picker);
                    return;
                }

                // Store data in hidden input for later restoration
                $dataInput.val(JSON.stringify(values));

                // Load attribute groups and values will be restored after loading
                self.loadCombinationAttributeGroups($picker);
                return;
            }

            if (!values.length) return;

            // Handle pattern type - load as tags
            // Supports both old format (array of strings) and new format (array of {pattern, caseSensitive})
            if (valueType === 'pattern') {
                values.forEach(function(item) {
                    if (typeof item === 'string' && item) {
                        // Old format: just a string (default to case-insensitive)
                        self.addPatternTag($picker, item, false);
                    } else if (item && item.pattern) {
                        // New format: object with pattern and caseSensitive
                        self.addPatternTag($picker, item.pattern, item.caseSensitive === true);
                    }
                });
                return;
            }

            // Handle entity_search type - load via AJAX
            var searchEntity = $picker.attr('data-search-entity') || blockType;

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'getTargetEntitiesByIds',
                    trait: 'EntitySelector',
                    entity_type: searchEntity,
                    ids: JSON.stringify(values)
                },
                success: function(response) {
                    if (response.success && response.entities) {
                        // Track which IDs were actually found (entities may have been deleted)
                        var validIds = [];
                        response.entities.forEach(function(entity) {
                            // Use addSelectionNoUpdate to avoid multiple visibility updates
                            self.addSelectionNoUpdate($picker, entity.id, entity.name, entity);
                            validIds.push(entity.id);
                        });

                        // Update chips visibility once after all chips are added
                        var $chips = $picker.find('.entity-chips');
                        self.updateChipsVisibility($chips);

                        // If some entities were not found, update the hidden input to remove orphaned IDs
                        if (validIds.length !== values.length) {
                            $dataInput.val(JSON.stringify(validIds));
                            // Re-serialize to update the main form data
                            self.serializeAllBlocks();
                        }

                        self.updateBlockStatus($picker.closest('.target-block'));
                    }
                }
            });
        },

        // =========================================================================
        // Pattern Tag Methods
        // =========================================================================

        /**
         * Add a pattern tag to the pattern chips container
         */
        addPatternTag: function($wrapper, pattern, caseSensitive) {
            var trans = this.config.trans || {};
            var $chipsContainer = $wrapper.find('.pattern-chips');
            // Default to case-insensitive (false)
            var isCaseSensitive = caseSensitive === true;
            var caseTitle = isCaseSensitive
                ? (trans.case_sensitive || 'Case sensitive - click to toggle')
                : (trans.case_insensitive || 'Case insensitive - click to toggle');

            var html = '<div class="pattern-tag' + (isCaseSensitive ? ' case-sensitive' : '') + '" data-pattern="' + this.escapeAttr(pattern) + '" data-case-sensitive="' + (isCaseSensitive ? '1' : '0') + '">';
            html += '<button type="button" class="btn-toggle-case" title="' + this.escapeAttr(caseTitle) + '">';
            html += '<span class="case-icon">' + (isCaseSensitive ? 'Aa' : 'aa') + '</span>';
            html += '</button>';
            html += '<span class="pattern-tag-text">' + this.escapeHtml(pattern) + '</span>';
            html += '<button type="button" class="btn-remove-pattern" title="' + this.escapeAttr(trans.remove_pattern || 'Remove pattern') + '">' + this.esIcon('delete') + '</button>';
            html += '</div>';
            $chipsContainer.append(html);
        },

        /**
         * Get all pattern tags from a wrapper
         * Returns array of objects: { pattern: string, caseSensitive: boolean }
         */
        getPatternTags: function($wrapper) {
            var patterns = [];
            // Exclude draft-tag which is the input field, not a saved pattern
            $wrapper.find('.pattern-tag:not(.draft-tag)').each(function() {
                var pattern = $(this).data('pattern');
                var caseSensitive = $(this).data('caseSensitive') === 1 || $(this).data('caseSensitive') === '1';
                if (pattern) {
                    patterns.push({
                        pattern: pattern,
                        caseSensitive: caseSensitive
                    });
                }
            });
            return patterns;
        },

        /**
         * Update the match count displayed in the draft tag while typing
         * Shows live preview with current case sensitivity setting
         */
        updateDraftTagCount: function($draftTag, pattern, caseSensitive) {
            var self = this;
            var $matchCount = $draftTag.find('.pattern-match-count');
            var $countValue = $matchCount.find('.count-value');

            // Get entity type from block
            var $block = $draftTag.closest('.target-block');
            var entityType = $block.data('blockType') || 'products';

            // Show loading - keep eye icon, update count value
            $countValue.html(this.esIcon('progress_activity', 'es-spin'));
            $matchCount.show();

            // Store pattern for click handler
            $matchCount.data('pattern', pattern);
            $matchCount.data('caseSensitive', caseSensitive);
            $matchCount.data('entityType', entityType);

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'countPatternMatches',
                    trait: 'EntitySelector',
                    pattern: pattern,
                    field: 'name',
                    entity_type: entityType,
                    case_sensitive: caseSensitive ? 1 : 0
                },
                success: function(response) {
                    if (response.success) {
                        var count = parseInt(response.count, 10) || 0;
                        $countValue.text(count);
                        $matchCount.show();
                        // Add visual feedback based on count
                        $matchCount.removeClass('count-zero count-found');
                        $matchCount.addClass(count === 0 ? 'count-zero' : 'count-found');
                        // Store count for preview
                        $matchCount.data('count', count);
                        // Update group total count to reflect draft pattern in calculation
                        var $group = $draftTag.closest('.selection-group');
                        if ($group.length) {
                            self.updateGroupTotalCount($group);
                        }
                    } else {
                        $countValue.text('?');
                        $matchCount.show();
                    }
                },
                error: function() {
                    $countValue.text('?');
                    $matchCount.show();
                }
            });
        },

        /**
         * Update condition count with a pending pattern (typed but not yet added as tag)
         * This shows a live preview of what the count would be if the user pressed Enter
         */
        updateConditionCountWithPendingPattern: function($row, pendingPattern) {
            var self = this;
            var trans = this.config.trans || {};

            // Find the count element - in method-selector-wrapper for include, in exclude-header-row for exclude
            var $countEl = $row.find('.method-selector-wrapper > .condition-match-count, > .exclude-header-row > .condition-match-count').first();
            if (!$countEl.length) return;

            var isExclude = $row.hasClass('exclude-row');
            var $methodSelect = isExclude
                ? $row.find('.exclude-method-select')
                : $row.find('.include-method-select');

            var method = $methodSelect.val();
            if (!method) {
                $countEl.hide();
                return;
            }

            var $picker = isExclude
                ? $row.find('.exclude-picker')
                : $row.find('.include-picker');

            var valueType = $picker.data('valueType') || 'none';

            // Only process for pattern value types
            if (valueType !== 'pattern') {
                return;
            }

            // Get existing pattern tags
            var values = this.getPatternTags($picker);

            // Add the pending pattern as a temporary tag (case-insensitive by default)
            if (pendingPattern) {
                values.push({ pattern: pendingPattern, caseSensitive: false });
            }

            if (values.length === 0) {
                $countEl.hide();
                return;
            }

            var $block = $row.closest('.target-block');
            var blockType = $block.data('blockType') || 'products';

            // Show loading
            $countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
            $countEl.removeClass('clickable no-matches').show();

            // Store condition data on badge for popover
            $countEl.data('conditionData', {
                method: method,
                values: values,
                blockType: blockType,
                isExclude: isExclude
            });

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'countConditionMatches',
                    trait: 'EntitySelector',
                    method: method,
                    values: JSON.stringify(values),
                    block_type: blockType
                },
                success: function(response) {
                    if (response && response.success) {
                        var count = response.count || 0;
                        $countEl.removeClass('no-matches clickable');
                        if (count === 0) {
                            $countEl.find('.preview-count').text(count);
                            $countEl.addClass('no-matches').show();
                        } else {
                            $countEl.find('.preview-count').text(count);
                            $countEl.addClass('clickable').show();
                        }
                    } else {
                        $countEl.hide().removeClass('clickable');
                    }
                },
                error: function() {
                    $countEl.hide().removeClass('clickable');
                }
            });
        },

        /**
         * Fetch pattern match count via AJAX
         */
        fetchPatternMatchCount: function($picker, pattern, $countEl) {
            // Determine field type from method select
            // Check if we're in an exclude row first, then fall back to include
            var $excludeRow = $picker.closest('.exclude-row');
            var $methodSelect;
            if ($excludeRow.length) {
                $methodSelect = $excludeRow.find('.exclude-method-select');
            } else {
                var $group = $picker.closest('.selection-group');
                $methodSelect = $group.find('.include-method-select');
            }
            var method = $methodSelect.val() || '';
            var field = method.indexOf('reference') !== -1 ? 'reference' : 'name';

            // Get entity type from block
            var $block = $picker.closest('.target-block');
            var entityType = $block.data('blockType') || 'products';

            // Show loading state
            $countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
            $countEl.removeClass('clickable no-matches').show();

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'countPatternMatches',
                    trait: 'EntitySelector',
                    pattern: pattern,
                    field: field,
                    entity_type: entityType,
                    case_sensitive: 0
                },
                success: function(response) {
                    if (response && response.success) {
                        var count = response.count || 0;
                        $countEl.find('.preview-count').text(count);
                        $countEl.removeClass('no-matches clickable').show();
                        if (count === 0) {
                            $countEl.addClass('no-matches');
                        } else {
                            $countEl.addClass('clickable');
                        }
                    } else {
                        $countEl.hide();
                    }
                },
                error: function() {
                    $countEl.hide();
                }
            });
        },

        // =========================================================================
        // Picker Value Extraction
        // =========================================================================

        /**
         * Get values from a picker based on its type
         */
        getPickerValues: function($picker, valueType) {
            switch (valueType) {
                case 'entity_search':
                    var ids = [];
                    $picker.find('.entity-chip').each(function() {
                        var id = $(this).data('id');
                        if (id) ids.push(id);
                    });
                    return ids;

                case 'pattern':
                    var patternValues = this.getPatternTags($picker);
                    // Also include draft pattern if it has content (not yet added as tag)
                    var $draftPatternInput = $picker.find('.draft-tag .pattern-input');
                    var draftPatternVal = $.trim($draftPatternInput.val());
                    if (draftPatternVal) {
                        var draftCaseSens = $draftPatternInput.closest('.draft-tag').attr('data-case-sensitive') === '1';
                        patternValues.push({
                            pattern: draftPatternVal,
                            caseSensitive: draftCaseSens
                        });
                    }
                    return patternValues;

                case 'numeric_range':
                    var min = $picker.find('.range-min-input').val();
                    var max = $picker.find('.range-max-input').val();
                    return { min: min || null, max: max || null };

                case 'date_range':
                    var from = $picker.find('.date-from-input').val();
                    var to = $picker.find('.date-to-input').val();
                    return { from: from || null, to: to || null };

                case 'select':
                    return [$picker.find('.select-value-input').val()];

                case 'boolean':
                    return [true];

                default:
                    return [];
            }
        },

        // =========================================================================
        // Count/Status Updates
        // =========================================================================

        /**
         * Fetch and update condition match count for a row (include or exclude)
         */
        updateConditionCount: function($row) {
            var self = this;
            var trans = this.config.trans || {};

            // Find the count element - in method-selector-wrapper for include, in exclude-header-row for exclude
            var $countEl = $row.find('.method-selector-wrapper > .condition-match-count, > .exclude-header-row > .condition-match-count').first();
            if (!$countEl.length) return;

            // Determine if this is an include or exclude row
            var isExclude = $row.hasClass('exclude-row');
            var $methodSelect = isExclude
                ? $row.find('.exclude-method-select')
                : $row.find('.include-method-select');

            var method = $methodSelect.val();
            if (!method) {
                $countEl.hide();
                return;
            }

            // Get the picker and extract values
            var $picker = isExclude
                ? $row.find('.exclude-picker')
                : $row.find('.include-picker');

            var valueType = $picker.data('valueType') || 'none';
            var values = this.getPickerValues($picker, valueType);

            // Don't count if no values (except for boolean/all methods)
            var hasNoValues = !values ||
                (Array.isArray(values) && values.length === 0) ||
                (typeof values === 'object' && !Array.isArray(values) && (
                    // For combination_attributes, check if attributes object is empty
                    (valueType === 'combination_attributes' && values.attributes !== undefined && Object.keys(values.attributes).length === 0) ||
                    // For other objects, check if completely empty
                    (valueType !== 'combination_attributes' && Object.keys(values).length === 0)
                ));
            if (valueType !== 'none' && valueType !== 'boolean' && hasNoValues) {
                $countEl.hide();
                return;
            }

            // Get block type
            var $block = $row.closest('.target-block');
            var blockType = $block.data('blockType') || 'products';

            // Show loading
            $countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
            $countEl.removeClass('clickable no-matches').show();

            // Store condition data on badge for popover
            $countEl.data('conditionData', {
                method: method,
                values: values,
                blockType: blockType,
                isExclude: isExclude
            });

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'countConditionMatches',
                    trait: 'EntitySelector',
                    method: method,
                    values: JSON.stringify(values),
                    block_type: blockType
                },
                success: function(response) {
                    if (response && response.success) {
                        var count = response.count || 0;
                        $countEl.removeClass('no-matches clickable');
                        if (count === 0) {
                            $countEl.find('.preview-count').text(count);
                            $countEl.addClass('no-matches').show();
                        } else {
                            // Show count, make clickable for preview popover
                            $countEl.find('.preview-count').text(count);
                            $countEl.addClass('clickable').show();
                        }
                    } else {
                        $countEl.hide().removeClass('clickable');
                    }
                },
                error: function() {
                    $countEl.hide().removeClass('clickable');
                }
            });
        },

        /**
         * Update all condition counts in a group
         */
        updateGroupCounts: function($group) {
            var self = this;

            // Update include count
            var $include = $group.find('.group-include');
            if ($include.length) {
                this.updateConditionCount($include);
            }

            // Update each exclude row count
            $group.find('.exclude-row').each(function() {
                self.updateConditionCount($(this));
            });

            // Update group total count (include - excludes)
            this.updateGroupTotalCount($group);
        },

        /**
         * Update the group total count badge (include - excludes)
         * Also updates the limit input placeholder
         */
        updateGroupTotalCount: function($group) {
            var self = this;
            var $block = $group.closest('.target-block');
            var blockType = $block.data('blockType') || 'products';
            var $badge = $group.find('.group-header .group-count-badge');
            var $limitInput = $group.find('.group-modifier-limit');

            // Build group data for AJAX
            var groupData = this.serializeGroup($group, blockType);

            // Check if include has valid data
            if (!groupData.include || !groupData.include.method) {
                $badge.hide();
                $limitInput.attr('placeholder', '–');
                return;
            }

            // Show loading
            $badge.html(this.esIcon('progress_activity', 'es-spin')).show();

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'countGroupItems',
                    trait: 'EntitySelector',
                    group_data: JSON.stringify(groupData),
                    block_type: blockType
                },
                success: function(response) {
                    if (response && response.success) {
                        var finalCount = response.final_count || 0;
                        var excludeCount = response.exclude_count || 0;

                        // Update badge with eye icon and count
                        var badgeHtml = self.esIcon('visibility') + ' ' + finalCount;
                        if (excludeCount > 0) {
                            badgeHtml += ' <span class="exclude-info">(-' + excludeCount + ')</span>';
                        }
                        $badge.html(badgeHtml);
                        $badge.addClass('clickable').show();

                        // Store group data on badge for preview popover
                        $badge.data('groupData', groupData);
                        $badge.data('blockType', blockType);
                        $badge.data('finalCount', finalCount);

                        // Update limit placeholder with the count
                        $limitInput.attr('placeholder', finalCount);

                        // Also update the group-preview-badge count (apply limit if set)
                        var $previewBadge = $group.find('.group-preview-badge .preview-count');
                        if ($previewBadge.length) {
                            var limit = parseInt($limitInput.val(), 10);
                            var displayCount = (limit > 0 && limit < finalCount) ? limit : finalCount;
                            $previewBadge.text(displayCount);
                        }
                    } else {
                        $badge.hide().removeClass('clickable');
                        $limitInput.attr('placeholder', '–');
                    }
                },
                error: function() {
                    $badge.hide();
                    $limitInput.attr('placeholder', '–');
                }
            });
        },

        /**
         * Update all condition counts for all visible groups
         */
        updateAllConditionCounts: function() {
            var self = this;
            this.$wrapper.find('.target-block.active .selection-group').each(function() {
                self.updateGroupCounts($(this));
            });
        },

        /**
         * Fetch category names by IDs and add chips to the picker
         * Used when adding selections from the tree modal
         * @param {jQuery} $picker - Picker element
         * @param {Array} ids - Category IDs to add
         * @param {string} entityType - 'categories' or 'cms_categories'
         * @param {Function} callback - Called when done
         */
        fetchCategoryNamesAndAddChips: function($picker, ids, entityType, callback) {
            var self = this;

            if (!ids || ids.length === 0) {
                if (typeof callback === 'function') {
                    callback();
                }
                return;
            }

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'getTargetEntitiesByIds',
                    trait: 'EntitySelector',
                    entity_type: entityType,
                    ids: JSON.stringify(ids)
                },
                success: function(response) {
                    if (response.success && response.entities) {
                        response.entities.forEach(function(entity) {
                            self.addSelectionNoUpdate($picker, entity.id, entity.name, entity);
                        });
                    }
                    if (typeof callback === 'function') {
                        callback();
                    }
                },
                error: function() {
                    if (typeof callback === 'function') {
                        callback();
                    }
                }
            });
        }
    };

})(jQuery);

/**
 * Entity Selector - Groups Module
 * Selection group management, serialization, block/tab management
 * @partial _groups.js
 *
 * Contains:
 * - Group management: addGroup, removeGroup, clearAllConditions
 * - Block/Tab: switchToBlock, updateTabBadges, updateBlockStatus
 * - Serialization: serializeGroup, serializeAllBlocks, getBlockGroups
 * - Counts: fetchProductCount, updateHeaderTotalCount, updateAllConditionCounts
 * - Excludes: addFirstExcludeRow, addExcludeRow, removeExcludeRow
 * - Validation: validate, showValidationError, clearValidationError
 */

(function($) {
    'use strict';

    window._EntitySelectorMixins = window._EntitySelectorMixins || {};

    window._EntitySelectorMixins.groups = {

        addGroup: function($block, blockType) {
            var $container = $block.find('.groups-container');
            var trans = this.config.trans || {};
            var blockDef = this.config.blocks[blockType] || {};
            var methods = blockDef.selection_methods || {};

            // Remove empty state
            $container.find('.groups-empty-state').remove();

            // Get next group index
            var maxIndex = -1;
            $container.find('.selection-group').each(function() {
                var idx = parseInt($(this).data('groupIndex'), 10);
                if (idx > maxIndex) maxIndex = idx;
            });
            var groupIndex = maxIndex + 1;

            // Build method options with optgroups
            var methodOptions = this.buildMethodOptions(methods, false);

            // Build exclude method options (no "all") with optgroups
            var excludeMethodOptions = this.buildMethodOptions(methods, true);

            var defaultGroupName = (trans.group || 'Group') + ' ' + (groupIndex + 1);
            var html = '<div class="selection-group" data-group-index="' + groupIndex + '" data-group-name="">';

            // Group header
            html += '<div class="group-header">';
            html += '<span class="group-collapse-toggle">' + this.esIcon('expand_less') + '</span>';
            html += '<span class="group-name-wrapper">';
            html += '<input type="text" class="group-name-input" value="" placeholder="' + defaultGroupName + '" title="' + (trans.click_to_name || 'Click to name this group') + '">';
            html += '<span class="group-count-badge" style="display:none;">' + this.esIcon('progress_activity', 'es-spin') + '</span>';
            html += '</span>';
            html += '<button type="button" class="btn-remove-group" title="' + (trans.remove_group || 'Remove group') + '">';
            html += this.esIcon('delete');
            html += '</button>';
            html += '</div>';

            // Group body (collapsible content)
            html += '<div class="group-body">';

            // Include section
            html += '<div class="group-include">';
            html += '<div class="section-row">';
            html += '<div class="method-selector-wrapper">';
            html += '<select class="include-method-select">' + methodOptions + '</select>';
            html += '<span class="condition-match-count no-matches">' + this.esIcon('visibility') + ' <span class="preview-count">0</span></span>';
            html += '<span class="method-info-placeholder"></span>';
            html += '</div>';
            var noItemsText = trans.no_items_selected || 'No items selected - use search below';
            html += '<div class="value-picker include-picker" style="display:none;" data-search-entity="' + blockType + '">';
            html += '<div class="entity-chips include-chips" data-placeholder="' + noItemsText + '"></div>';
            html += '<div class="entity-search-box">';
            html += this.esIcon('search', 'entity-search-icon');
            html += '<input type="text" class="entity-search-input" placeholder="' + (trans.search_placeholder || 'Search by name, reference, ID...') + '" autocomplete="off">';
            html += '<span class="search-loading" style="display:none;">' + this.esIcon('progress_activity', 'es-spin') + '</span>';
            html += '</div>';
            html += '<input type="hidden" class="include-values-data" value="[]">';
            html += '</div>';
            html += '</div>';
            html += '</div>';

            // Excludes section (collapsed by default)
            html += '<div class="group-excludes">';
            html += '<button type="button" class="btn-add-exclude">';
            html += this.esIcon('add') + ' ' + (trans.add_exceptions || 'Add exceptions');
            html += '</button>';
            html += '</div>';

            // Group-level modifiers (limit & sort)
            html += '<div class="group-modifiers">';
            html += '<span class="modifier-inline modifier-limit">';
            html += '<span class="modifier-label">' + (trans.limit || 'Limit') + '</span>';
            html += '<input type="number" class="group-modifier-limit mpr-input-compact" placeholder="–" min="1" step="1" title="' + (trans.limit_tooltip || 'Max items to return (empty = all)') + '">';
            html += '</span>';
            html += '<span class="modifier-inline modifier-sort">';
            html += '<span class="modifier-label">' + (trans.sort || 'Sort') + '</span>';
            html += '<select class="group-modifier-sort mpr-input-compact">';
            html += '<option value="sales" selected>' + (trans.sort_bestsellers || 'Best sellers') + '</option>';
            html += '<option value="date_add">' + (trans.sort_newest || 'Newest') + '</option>';
            html += '<option value="price">' + (trans.sort_price || 'Price') + '</option>';
            html += '<option value="name">' + (trans.sort_name || 'Name') + '</option>';
            html += '<option value="position">' + (trans.sort_position || 'Position') + '</option>';
            html += '<option value="quantity">' + (trans.sort_stock || 'Stock quantity') + '</option>';
            html += '<option value="random">' + (trans.sort_random || 'Random') + '</option>';
            html += '</select>';
            html += '<button type="button" class="btn-sort-dir" data-dir="DESC" title="' + (trans.sort_direction || 'Sort direction') + '">';
            html += this.esIcon('sort');
            html += '</button>';
            html += '</span>';
            html += '<span class="group-preview-badge clickable" title="' + (trans.preview_results || 'Preview results') + '">';
            html += this.esIcon('visibility') + ' <span class="preview-count"></span>';
            html += '</span>';
            html += '</div>';

            html += '</div>'; // Close group-body

            html += '</div>'; // Close selection-group

            $container.append(html);

            // Find the new group and set method to "all" by default
            var $newGroup = $container.find('.selection-group[data-group-index="' + groupIndex + '"]');

            // Enhance the method select with styled dropdown
            this.enhanceMethodSelect($newGroup.find('.include-method-select'));

            $newGroup.find('.include-method-select').val('all').trigger('change');

            this.updateBlockStatus($block);
            this.serializeAllBlocks();
        },

        removeGroup: function($group, $block) {
            $group.remove();

            var $container = $block.find('.groups-container');
            var remainingGroups = $container.find('.selection-group').length;

            if (remainingGroups === 0) {
                var emptyText = this.getEmptyStateText($block);
                var emptyHtml = '<div class="groups-empty-state">';
                emptyHtml += '<span class="empty-state-text">' + emptyText + '</span>';
                emptyHtml += '</div>';
                $container.html(emptyHtml);
            }

            this.updateBlockStatus($block);
            this.serializeAllBlocks();

            // Update tab badges and header total count
            this.updateTabBadges();
        },

        clearAllConditions: function() {
            var self = this;

            // Remove all groups from all blocks
            this.$wrapper.find('.target-block').each(function() {
                var $block = $(this);
                var $container = $block.find('.groups-container');

                // Remove all groups
                $container.find('.selection-group').remove();

                // Show empty state
                var emptyText = self.getEmptyStateText($block);
                var emptyHtml = '<div class="groups-empty-state">';
                emptyHtml += '<span class="empty-state-text">' + emptyText + '</span>';
                emptyHtml += '</div>';
                $container.html(emptyHtml);

                self.updateBlockStatus($block);
            });

            // Update serialized data
            this.serializeAllBlocks();

            // Update tab badges and header count
            this.updateTabBadges();

            // Also update header total count immediately (since all cleared)
            this.updateHeaderTotalCount();
        },

        switchToBlock: function(blockType) {
            // Update tabs
            this.$wrapper.find('.target-block-tab').removeClass('active');
            this.$wrapper.find('.target-block-tab[data-block-type="' + blockType + '"]').addClass('active');

            // Update blocks
            this.$wrapper.find('.target-block').removeClass('active').hide();
            this.$wrapper.find('.target-block[data-block-type="' + blockType + '"]').addClass('active').show();

            // Close dropdown if open
            this.hideDropdown();
        },

        updateTabBadges: function() {
            var self = this;

            // Collect all block types with data and set loading state
            var blockTypesWithData = [];
            this.$wrapper.find('.target-block-tab').each(function() {
                var $tab = $(this);
                var blockType = $tab.data('blockType');
                var $block = self.$wrapper.find('.target-block[data-block-type="' + blockType + '"]');
                var groupCount = $block.find('.selection-group').length;

                // Update or add badge
                var $badge = $tab.find('.tab-badge');
                if (groupCount > 0) {
                    // Show loading state first
                    if ($badge.length) {
                        $badge.addClass('loading').html(self.esIcon('progress_activity', 'es-spin'));
                    } else {
                        $tab.append('<span class="tab-badge loading">' + self.esIcon('progress_activity', 'es-spin') + '</span>');
                    }
                    $tab.addClass('has-data');
                    blockTypesWithData.push(blockType);
                } else if ($block.hasClass('custom-block')) {
                    // Custom blocks: check if any input/textarea/select has a non-empty value
                    var hasCustomValue = false;
                    $block.find('.custom-block-content').find('input, textarea, select').each(function() {
                        if ($(this).val() && $(this).val().trim() !== '') {
                            hasCustomValue = true;
                            return false;
                        }
                    });
                    if (hasCustomValue) {
                        if ($badge.length) {
                            $badge.removeClass('loading').html(self.esIcon('check'));
                        } else {
                            $tab.append('<span class="tab-badge">' + self.esIcon('check') + '</span>');
                        }
                        $tab.addClass('has-data');
                    } else {
                        $badge.remove();
                        $tab.removeClass('has-data');
                    }
                } else {
                    $badge.remove();
                    $tab.removeClass('has-data');
                }
            });

            // Update target switch state based on whether any data exists
            this.updateTargetSwitchState();

            // Fetch all counts in a single bulk request
            if (blockTypesWithData.length > 0) {
                this.fetchAllCounts(blockTypesWithData);
            }
        },

        updateTargetSwitchState: function() {
            var $switch = this.$wrapper.find('.prestashop-switch');
            if (!$switch.length) {
                return;
            }

            // Check if any block has data
            var hasData = false;
            this.$wrapper.find('.target-block').each(function() {
                if ($(this).find('.selection-group').length > 0) {
                    hasData = true;
                    return false; // break
                }
            });

            // Update switch: value="1" is "Everyone/All/None", value="0" is "Specific/Selected"
            if (hasData) {
                $switch.find('input[value="0"]').prop('checked', true);
            } else {
                $switch.find('input[value="1"]').prop('checked', true);
            }
        },

        /**
         * Fetch counts for all block types in a single bulk AJAX request
         * @param {Array} blockTypes - Array of block type strings to fetch counts for
         */
        fetchAllCounts: function(blockTypes) {
            var self = this;

            // Read saved data from hidden input
            var $hiddenInput = this.$wrapper.find('input[name="' + this.config.name + '"]');
            var savedData = {};
            try {
                savedData = JSON.parse($hiddenInput.val() || '{}');
            } catch (e) {
                savedData = {};
            }

            // Build conditions object for all requested block types
            var conditions = {};
            blockTypes.forEach(function(blockType) {
                var groups = (savedData[blockType] && savedData[blockType].groups) ? savedData[blockType].groups : [];
                if (groups.length > 0) {
                    conditions[blockType] = { groups: groups };
                }
            });

            // If no valid conditions, remove loading spinners
            if (Object.keys(conditions).length === 0) {
                blockTypes.forEach(function(blockType) {
                    var $tab = self.$wrapper.find('.target-block-tab[data-block-type="' + blockType + '"]');
                    $tab.find('.tab-badge').remove();
                    $tab.removeClass('has-data');
                });
                return;
            }

            // Single bulk AJAX request for all counts
            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'previewEntitySelectorBulk',
                    trait: 'EntitySelector',
                    conditions: JSON.stringify(conditions)
                },
                success: function(response) {
                    if (response.success && response.counts) {
                        // Update each tab with its count
                        Object.keys(response.counts).forEach(function(blockType) {
                            var count = response.counts[blockType];
                            var $tab = self.$wrapper.find('.target-block-tab[data-block-type="' + blockType + '"]');
                            var $badge = $tab.find('.tab-badge');

                            if ($badge.length) {
                                $badge.removeClass('loading').html(self.esIcon('visibility') + ' ' + count);
                                // Store preview data for later popover use
                                $tab.data('previewData', { count: count, success: true });
                            }
                        });

                        // Handle any block types not in response (set count to 0 or remove badge)
                        blockTypes.forEach(function(blockType) {
                            if (!(blockType in response.counts)) {
                                var $tab = self.$wrapper.find('.target-block-tab[data-block-type="' + blockType + '"]');
                                $tab.find('.tab-badge').remove();
                                $tab.removeClass('has-data');
                            }
                        });

                        self.updateHeaderTotalCount();
                    } else {
                        console.error('[EntitySelector] Bulk preview failed:', response.error || 'Unknown error');
                        // Remove loading spinners on error
                        blockTypes.forEach(function(blockType) {
                            var $tab = self.$wrapper.find('.target-block-tab[data-block-type="' + blockType + '"]');
                            $tab.find('.tab-badge').remove();
                        });
                    }
                },
                error: function(xhr, status, error) {
                    console.error('[EntitySelector] Bulk AJAX error:', status, error);
                    // Remove loading spinners on error
                    blockTypes.forEach(function(blockType) {
                        var $tab = self.$wrapper.find('.target-block-tab[data-block-type="' + blockType + '"]');
                        $tab.find('.tab-badge').remove();
                    });
                }
            });
        },

        /**
         * Fetch count for a single block type (legacy, used for single updates)
         */
        fetchProductCount: function(blockType, $tab) {
            var self = this;
            var data = {};

            // Read from hidden input (contains full saved data or freshly serialized data)
            var $hiddenInput = this.$wrapper.find('input[name="' + this.config.name + '"]');
            var savedData = {};
            try {
                savedData = JSON.parse($hiddenInput.val() || '{}');
            } catch (e) {
                savedData = {};
            }

            // Get groups for the requested block type
            var groups = (savedData[blockType] && savedData[blockType].groups) ? savedData[blockType].groups : [];

            if (groups.length === 0) {
                $tab.find('.tab-badge').remove();
                $tab.removeClass('has-data');
                $tab.removeData('previewData');
                return;
            }

            // Show loading state
            var $badge = $tab.find('.tab-badge');
            if (!$badge.length) {
                $badge = $('<span class="tab-badge loading">' + this.esIcon('progress_activity', 'es-spin') + '</span>');
                $tab.append($badge);
            } else {
                $badge.addClass('loading').html(this.esIcon('progress_activity', 'es-spin'));
            }
            $tab.addClass('has-data');

            data[blockType] = { groups: groups };

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'previewEntitySelector',
                    trait: 'EntitySelector',
                    conditions: JSON.stringify(data),
                    block_type: blockType,
                    limit: 10
                },
                success: function(response) {
                    if (response.success) {
                        var $badge = $tab.find('.tab-badge');
                        $badge.removeClass('loading').html(self.esIcon('visibility') + ' ' + response.count);

                        // Store preview data for popover
                        $tab.data('previewData', response);

                        // Update header total count
                        self.updateHeaderTotalCount();
                    } else {
                        console.error('[EntitySelector] Preview failed for', blockType, ':', response.error || 'Unknown error');
                        $tab.find('.tab-badge').remove();
                    }
                },
                error: function(xhr, status, error) {
                    console.error('[EntitySelector] AJAX error for', blockType, ':', status, error);
                    $tab.find('.tab-badge').remove();
                    self.updateHeaderTotalCount();
                }
            });
        },

        updateHeaderTotalCount: function() {
            var self = this;
            var total = 0;

            // Sum up all tab badge counts
            this.$wrapper.find('.target-block-tab .tab-badge').each(function() {
                var $badge = $(this);
                if (!$badge.hasClass('loading')) {
                    var count = parseInt($badge.text(), 10);
                    if (!isNaN(count)) {
                        total += count;
                    }
                }
            });

            var $totalBadge = this.$wrapper.find('.trait-total-count');
            if (total > 0) {
                $totalBadge.find('.count-value').text(total);
                $totalBadge.show();
            } else {
                $totalBadge.hide();
            }

            // Update show-all toggle state
            this.updateShowAllToggle();
        },

        updateShowAllToggle: function() {
            var $toggle = this.$wrapper.find('.trait-show-all-toggle');
            if (!$toggle.length) return;

            var $checkbox = $toggle.find('.show-all-checkbox');
            var hasData = this.$wrapper.find('.target-block-tab.has-data').length > 0;

            // If there's data, uncheck (not showing to all), otherwise check
            $checkbox.prop('checked', !hasData);
        },

        updateBlockStatus: function($block) {
            var $status = $block.find('.block-status');
            var blockType = $block.data('blockType');
            var blockDef = this.config.blocks[blockType] || {};
            var trans = this.config.trans || {};

            var groups = this.getBlockGroups($block);

            if (groups.length === 0) {
                var emptyMeansAll = this.config.emptyMeansAll !== false;
                if (emptyMeansAll) {
                    $status.text((trans.all || 'All') + ' ' + (blockDef.entity_label_plural || 'items'));
                } else {
                    $status.text(trans.nothing_selected || 'Nothing selected');
                }
            } else {
                $status.text(groups.length + ' ' + (groups.length === 1 ? (trans.group || 'group') : (trans.groups || 'groups')));
            }
        },

        getEmptyStateText: function($block) {
            var blockType = $block.data('blockType');
            var blockMode = $block.data('mode') || 'multi';
            var blockDef = this.config.blocks[blockType] || {};
            var trans = this.config.trans || {};
            var emptyMeansAll = this.config.emptyMeansAll !== false;

            if (blockMode === 'single') {
                return trans.no_item_selected || 'No item selected';
            }

            if (emptyMeansAll) {
                return (trans.all || 'All') + ' ' + (blockDef.entity_label_plural || 'items') + ' ' + (trans.included || 'included');
            }

            return trans.nothing_selected || 'Nothing selected';
        },

        serializeGroup: function($group, blockType) {
            var self = this;

            // Include
            var includeMethod = $group.find('.include-method-select').val() || 'all';
            var $includePicker = $group.find('.include-picker');
            var includeValues = this.getPickerValues($includePicker);

            // Excludes (multiple rows)
            var excludes = [];
            var $excludesSection = $group.find('.group-excludes.has-excludes');
            if ($excludesSection.length) {
                $group.find('.exclude-row').each(function() {
                    var $row = $(this);
                    var excludeMethod = $row.find('.exclude-method-select').val() || null;
                    var $excludePicker = $row.find('.exclude-picker');
                    var excludeValues = self.getPickerValues($excludePicker);

                    if (excludeMethod && excludeValues && (Array.isArray(excludeValues) ? excludeValues.length > 0 : true)) {
                        excludes.push({
                            method: excludeMethod,
                            values: excludeValues
                        });
                    }
                });
            }

            var groupData = {
                include: {
                    method: includeMethod,
                    values: includeValues
                }
            };

            if (excludes.length > 0) {
                groupData.excludes = excludes;
            }

            // Add modifiers if present
            var modifiers = this.getGroupModifiers($group);
            if (modifiers.limit || modifiers.sort_by) {
                groupData.modifiers = modifiers;
            }

            return groupData;
        },

        serializeAllBlocks: function($changedRow) {
            var self = this;
            var data = {};

            console.log('[EntitySelector] serializeAllBlocks called');

            this.$wrapper.find('.target-block').each(function() {
                var $block = $(this);
                var blockType = $block.data('blockType');
                var groups = self.getBlockGroups($block);

                console.log('[EntitySelector] Block:', blockType, 'Groups:', groups.length);

                // Groups now contain their own modifiers, no block-level modifiers
                if (groups.length > 0) {
                    data[blockType] = { groups: groups };
                }

                self.updateBlockStatus($block);
            });

            // Update hidden input first
            var $input = this.$wrapper.find('input[name="' + this.config.name + '"]');
            var jsonData = JSON.stringify(data);

            console.log('[EntitySelector] Hidden input name:', this.config.name);
            console.log('[EntitySelector] Hidden input found:', $input.length);
            console.log('[EntitySelector] Serialized data:', jsonData.substring(0, 500));

            $input.val(jsonData);

            // Then update tab badges (reads from hidden input)
            this.updateTabBadges();

            // Debounced update of condition count - only for changed row if specified
            if (this.countUpdateTimeout) {
                clearTimeout(this.countUpdateTimeout);
            }
            this.countUpdateTimeout = setTimeout(function() {
                if ($changedRow && $changedRow.length) {
                    // Update the specific row that changed
                    self.updateConditionCount($changedRow);
                    // Also update the group total count (include - excludes)
                    var $group = $changedRow.closest('.selection-group');
                    if ($group.length) {
                        self.updateGroupTotalCount($group);
                    }
                } else {
                    // Fallback: update all counts (initial load, structure changes)
                    self.updateAllConditionCounts();
                }
            }, 500);
        },

        getBlockGroups: function($block) {
            var self = this;
            var groups = [];

            $block.find('.selection-group').each(function() {
                var $group = $(this);

                // Include
                var includeMethod = $group.find('.include-method-select').val() || 'all';
                var $includePicker = $group.find('.include-picker');
                var includeValues = self.getPickerValues($includePicker);

                // Skip groups with invalid include conditions (e.g., "specific products" with none selected)
                if (!self.isConditionValid(includeMethod, includeValues, $includePicker)) {
                    return true; // continue to next group
                }

                // Excludes (multiple rows) - only include valid ones
                var excludes = [];
                var $excludesSection = $group.find('.group-excludes.has-excludes');
                if ($excludesSection.length) {
                    $group.find('.exclude-row').each(function() {
                        var $row = $(this);
                        var excludeMethod = $row.find('.exclude-method-select').val() || null;
                        var $excludePicker = $row.find('.exclude-picker');
                        var excludeValues = self.getPickerValues($excludePicker);

                        // Only include valid exclude conditions
                        if (excludeMethod && self.isConditionValid(excludeMethod, excludeValues, $excludePicker)) {
                            excludes.push({
                                method: excludeMethod,
                                values: excludeValues
                            });
                        }
                    });
                }

                var groupData = {
                    include: {
                        method: includeMethod,
                        values: includeValues
                    }
                };

                // Group name (optional, for organizational purposes)
                var groupName = $.trim($group.attr('data-group-name') || '');
                if (groupName) {
                    groupData.name = groupName;
                }

                if (excludes.length > 0) {
                    groupData.excludes = excludes;
                }

                // Group-level modifiers
                var modifiers = self.getGroupModifiers($group);
                if (modifiers.limit || modifiers.sort_by) {
                    groupData.modifiers = modifiers;
                }

                groups.push(groupData);
            });

            return groups;
        },

        getGroupModifiers: function($group) {
            var limit = $group.find('.group-modifier-limit').val();
            var sortBy = $group.find('.group-modifier-sort').val() || 'sales';
            var $sortDirBtn = $group.find('.group-modifiers .btn-sort-dir');
            var sortDir = $sortDirBtn.data('dir') || 'DESC';

            return {
                limit: limit ? parseInt(limit, 10) : null,
                sort_by: sortBy || null,
                sort_dir: sortDir || 'DESC'
            };
        },

        getPickerValues: function($picker) {
            var valueType = $picker.attr('data-value-type') || 'entity_search';
            var values = [];

            switch (valueType) {
                case 'entity_search':
                    $picker.find('.entity-chip').each(function() {
                        var id = $(this).data('id');
                        values.push(isNaN(id) ? id : Number(id));
                    });
                    break;

                case 'pattern':
                    values = this.getPatternTags($picker);
                    // Also include draft pattern if it has content (not yet added as tag)
                    var $draftInput = $picker.find('.draft-tag .pattern-input');
                    var draftPattern = $.trim($draftInput.val());
                    if (draftPattern) {
                        var draftCaseSensitive = $draftInput.closest('.draft-tag').attr('data-case-sensitive') === '1';
                        values.push({
                            pattern: draftPattern,
                            caseSensitive: draftCaseSensitive
                        });
                    }
                    break;

                case 'numeric_range':
                    var min = $picker.find('.range-min-input').val();
                    var max = $picker.find('.range-max-input').val();
                    if (min !== '' || max !== '') {
                        values = {
                            min: min !== '' ? parseFloat(min) : null,
                            max: max !== '' ? parseFloat(max) : null
                        };
                    }
                    break;

                case 'date_range':
                    var from = $picker.find('.date-from-input').val();
                    var to = $picker.find('.date-to-input').val();
                    if (from || to) {
                        values = {
                            from: from || null,
                            to: to || null
                        };
                    }
                    break;

                case 'select':
                    var selectVal = $picker.find('.select-value-input').val();
                    if (selectVal) {
                        values = [selectVal];
                    }
                    break;

                case 'boolean':
                    values = [true];
                    break;

                case 'multi_numeric_range':
                    var ranges = [];
                    $picker.find('.range-chip').each(function() {
                        var $chip = $(this);
                        var minVal = $chip.data('min');
                        var maxVal = $chip.data('max');
                        ranges.push({
                            min: minVal !== '' && minVal !== undefined ? parseFloat(minVal) : null,
                            max: maxVal !== '' && maxVal !== undefined ? parseFloat(maxVal) : null
                        });
                    });
                    if (ranges.length > 0) {
                        values = ranges;
                    }
                    break;

                case 'multi_select_tiles':
                    $picker.find('.tile-option.selected').each(function() {
                        values.push($(this).data('value'));
                    });
                    break;

                case 'combination_attributes':
                    // Returns object: { mode: 'products'|'combinations', attributes: { groupId: [valueId1, valueId2], ... } }
                    var combAttrs = {};
                    $picker.find('.comb-attr-value.selected').each(function() {
                        var groupId = $(this).data('groupId').toString();
                        var valueId = $(this).data('valueId');
                        if (!combAttrs[groupId]) {
                            combAttrs[groupId] = [];
                        }
                        combAttrs[groupId].push(valueId);
                    });
                    if (Object.keys(combAttrs).length > 0) {
                        // Get mode: from radio if toggle exists, otherwise from config
                        var $combPicker = $picker.find('.combination-attributes-picker');
                        var configMode = $combPicker.data('combinationMode') || this.config.combinationMode || 'products';
                        var combMode;
                        if (configMode === 'toggle') {
                            combMode = $picker.find('.comb-mode-radio:checked').val() || 'products';
                        } else {
                            combMode = configMode;
                        }
                        values = {
                            mode: combMode,
                            attributes: combAttrs
                        };
                    }
                    break;
            }

            return values;
        },

        isConditionValid: function(method, values, $picker) {
            // 'all' method never needs values
            if (method === 'all') {
                return true;
            }

            // Boolean methods are always valid (the value is implicit true)
            var valueType = $picker.attr('data-value-type') || 'entity_search';
            if (valueType === 'boolean') {
                return true;
            }

            // For other methods, check if values are meaningful
            if (Array.isArray(values)) {
                return values.length > 0;
            }

            // For object values (ranges, combination_attributes), check if meaningful
            if (typeof values === 'object' && values !== null) {
                // Special handling for combination_attributes: { mode, attributes }
                if (valueType === 'combination_attributes' && values.attributes !== undefined) {
                    return Object.keys(values.attributes).length > 0;
                }
                // For ranges and other objects, check if at least one bound is set
                return Object.keys(values).some(function(key) {
                    return values[key] !== null && values[key] !== '';
                });
            }

            return false;
        },

        /**
         * Update all condition counts using a single bulk AJAX request
         */
        updateAllConditionCounts: function() {
            var self = this;
            var conditions = {};
            var conditionElements = {};
            var conditionIndex = 0;

            // Collect all conditions from all active groups
            this.$wrapper.find('.target-block.active .selection-group').each(function() {
                var $group = $(this);
                var $block = $group.closest('.target-block');
                var blockType = $block.data('blockType') || 'products';

                // Process include row
                var $include = $group.find('.group-include');
                if ($include.length) {
                    var includeData = self.getConditionData($include, blockType);
                    if (includeData) {
                        var id = 'c' + conditionIndex++;
                        conditions[id] = includeData.condition;
                        conditionElements[id] = includeData.$countEl;
                    }
                }

                // Process exclude rows
                $group.find('.exclude-row').each(function() {
                    var excludeData = self.getConditionData($(this), blockType);
                    if (excludeData) {
                        var id = 'c' + conditionIndex++;
                        conditions[id] = excludeData.condition;
                        conditionElements[id] = excludeData.$countEl;
                    }
                });
            });

            // If no conditions, nothing to do
            if (Object.keys(conditions).length === 0) {
                return;
            }

            // Make single bulk AJAX request
            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'countConditionMatchesBulk',
                    trait: 'EntitySelector',
                    conditions: JSON.stringify(conditions)
                },
                success: function(response) {
                    if (response && response.success && response.counts) {
                        // Update each count element with its result
                        Object.keys(response.counts).forEach(function(id) {
                            var count = response.counts[id] || 0;
                            var $countEl = conditionElements[id];
                            if ($countEl && $countEl.length) {
                                $countEl.removeClass('no-matches clickable');
                                if (count === 0) {
                                    $countEl.find('.preview-count').text(count);
                                    $countEl.addClass('no-matches').show();
                                } else {
                                    $countEl.find('.preview-count').text(count);
                                    $countEl.addClass('clickable').show();
                                }
                            }
                        });
                    }
                    // Note: Group totals are updated on-demand when user interacts, not on initial load
                },
                error: function() {
                    // Hide all count elements on error
                    Object.keys(conditionElements).forEach(function(id) {
                        var $countEl = conditionElements[id];
                        if ($countEl && $countEl.length) {
                            $countEl.hide().removeClass('clickable');
                        }
                    });
                }
            });
        },

        /**
         * Extract condition data from a row for bulk counting
         */
        getConditionData: function($row, blockType) {
            console.log('[getConditionData] Called with blockType:', blockType);
            var $countEl = $row.find('.method-selector-wrapper > .condition-match-count, > .exclude-header-row .condition-match-count').first();
            console.log('[getConditionData] $countEl found:', $countEl.length);
            if (!$countEl.length) return null;

            var isExclude = $row.hasClass('exclude-row');
            var $methodSelect = isExclude
                ? $row.find('.exclude-method-select')
                : $row.find('.include-method-select');

            var method = $methodSelect.val();
            console.log('[getConditionData] method:', method);
            if (!method) {
                $countEl.hide();
                return null;
            }

            var $picker = isExclude
                ? $row.find('.exclude-picker')
                : $row.find('.include-picker');
            console.log('[getConditionData] $picker found:', $picker.length, 'data-value-type attr:', $picker.attr('data-value-type'));

            var valueType = $picker.data('valueType') || $picker.attr('data-value-type') || 'none';
            console.log('[getConditionData] valueType:', valueType);

            // Special case: "All countries" method - needs separate handling for holidays
            if (valueType === 'none' && blockType === 'countries' && method === 'all') {
                console.log('[getConditionData] All countries detected - triggering updateConditionCount');
                // Trigger separate update for this special case (uses nested AJAX)
                var self = this;
                setTimeout(function() {
                    self.updateConditionCount($row, blockType);
                }, 0);
                return null; // Skip bulk processing, handled separately
            }

            // Special case: Specific countries with entity_search - needs holiday counting, not entity counting
            var searchEntity = $picker.attr('data-search-entity') || '';
            if (blockType === 'countries' && valueType === 'entity_search' && searchEntity === 'countries') {
                console.log('[getConditionData] Specific countries detected - triggering updateConditionCount for holiday counting');
                var self = this;
                setTimeout(function() {
                    self.updateConditionCount($row, blockType);
                }, 0);
                return null; // Skip bulk processing, handled separately
            }

            // Hide badge for other "all" type methods (valueType === 'none') since they don't filter
            if (valueType === 'none') {
                $countEl.hide();
                return null;
            }

            var values = this.getPickerValues($picker, valueType);

            // Don't count if no values (except for boolean methods)
            var hasNoValues = !values ||
                (Array.isArray(values) && values.length === 0) ||
                (typeof values === 'object' && !Array.isArray(values) && (
                    (valueType === 'combination_attributes' && values.attributes !== undefined && Object.keys(values.attributes).length === 0) ||
                    (valueType !== 'combination_attributes' && Object.keys(values).length === 0)
                ));
            if (valueType !== 'boolean' && hasNoValues) {
                $countEl.hide();
                return null;
            }

            // Show loading spinner
            $countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
            $countEl.removeClass('clickable no-matches').show();

            // Store condition data on badge for popover
            $countEl.data('conditionData', {
                method: method,
                values: values,
                blockType: blockType,
                isExclude: isExclude
            });

            return {
                condition: {
                    method: method,
                    values: values,
                    block_type: blockType
                },
                $countEl: $countEl
            };
        },

        updateGroupCounts: function($group) {
            var self = this;
            var $block = $group.closest('.target-block');
            var blockType = $block.data('blockType') || 'products';

            // Update include count
            var $include = $group.find('.group-include');
            if ($include.length) {
                this.updateConditionCount($include, blockType);
            }

            // Update each exclude row count
            $group.find('.exclude-row').each(function() {
                self.updateConditionCount($(this), blockType);
            });

            // Update group total count (include - excludes)
            this.updateGroupTotalCount($group);
        },

        /**
         * Update a single condition count (used for individual updates after user changes)
         */
        updateConditionCount: function($row, blockType) {
            var self = this;

            var $countEl = $row.find('.method-selector-wrapper > .condition-match-count, > .exclude-header-row .condition-match-count').first();
            if (!$countEl.length) {
                console.log('[updateConditionCount] No $countEl found');
                return;
            }

            var isExclude = $row.hasClass('exclude-row');
            var $methodSelect = isExclude
                ? $row.find('.exclude-method-select')
                : $row.find('.include-method-select');

            var method = $methodSelect.val();
            console.log('[updateConditionCount] method:', method, 'isExclude:', isExclude);
            if (!method) {
                console.log('[updateConditionCount] No method, hiding badge');
                $countEl.hide();
                return;
            }

            var $picker = isExclude
                ? $row.find('.exclude-picker')
                : $row.find('.include-picker');

            var valueType = $picker.data('valueType') || 'none';
            var searchEntity = $picker.attr('data-search-entity') || '';

            // Get the block type to check if this is a countries block
            if (!blockType) {
                var $block = $row.closest('.target-block');
                blockType = $block.data('blockType') || 'products';
            }

            console.log('[updateConditionCount] valueType:', valueType, 'searchEntity:', searchEntity, 'blockType:', blockType, 'method:', method);

            // Special case: "All countries" method - fetch holidays for all countries
            if (valueType === 'none' && blockType === 'countries' && method === 'all') {
                console.log('[updateConditionCount] All countries method - fetching all country holidays');
                $countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
                $countEl.removeClass('clickable no-matches country-holidays').show();

                // First fetch all active country IDs, then get holidays
                $.ajax({
                    url: self.config.ajaxUrl,
                    type: 'POST',
                    dataType: 'json',
                    data: {
                        ajax: 1,
                        action: 'searchTargetEntities',
                        trait: 'EntitySelector',
                        entity_type: 'countries',
                        query: '',
                        limit: 500
                    },
                    success: function(response) {
                        var items = response.results || response.items || [];
                        if (response && response.success && items.length > 0) {
                            var allCountryIds = items.map(function(item) { return item.id; });
                            console.log('[updateConditionCount] Found', allCountryIds.length, 'countries, fetching holidays');

                            // Store condition data for click handler
                            $countEl.data('conditionData', {
                                method: method,
                                values: allCountryIds,
                                blockType: blockType,
                                isExclude: isExclude,
                                isCountryHolidays: true,
                                countryIds: allCountryIds,
                                isAllCountries: true
                            });

                            // Now fetch holiday count
                            $.ajax({
                                url: self.config.ajaxUrl,
                                type: 'POST',
                                dataType: 'json',
                                data: {
                                    ajax: 1,
                                    action: 'getHolidaysForCountries',
                                    trait: 'EntitySelector',
                                    country_ids: allCountryIds.join(','),
                                    count_only: 1
                                },
                                success: function(holidayResponse) {
                                    console.log('[updateConditionCount] All countries holiday response:', holidayResponse);
                                    if (holidayResponse && holidayResponse.success) {
                                        var count = holidayResponse.total_count || 0;
                                        $countEl.removeClass('no-matches clickable');
                                        $countEl.addClass('country-holidays');
                                        if (count === 0) {
                                            $countEl.find('.preview-count').text(count);
                                            $countEl.addClass('no-matches').show();
                                        } else {
                                            $countEl.find('.preview-count').text(count);
                                            $countEl.addClass('clickable').show();
                                        }
                                        $countEl.data('countriesInfo', holidayResponse.countries || []);
                                    } else {
                                        $countEl.hide().removeClass('clickable');
                                    }
                                },
                                error: function() {
                                    $countEl.hide().removeClass('clickable');
                                }
                            });
                        } else {
                            $countEl.hide().removeClass('clickable');
                        }
                    },
                    error: function() {
                        $countEl.hide().removeClass('clickable');
                    }
                });
                return;
            }

            // Hide badge for other "all" type methods (valueType === 'none') since they don't filter
            if (valueType === 'none') {
                console.log('[updateConditionCount] valueType is none, hiding badge');
                $countEl.hide();
                return;
            }

            var values = this.getPickerValues($picker, valueType);

            var hasNoValues = !values ||
                (Array.isArray(values) && values.length === 0) ||
                (typeof values === 'object' && !Array.isArray(values) && (
                    (valueType === 'combination_attributes' && values.attributes !== undefined && Object.keys(values.attributes).length === 0) ||
                    (valueType !== 'combination_attributes' && Object.keys(values).length === 0)
                ));
            if (valueType !== 'boolean' && hasNoValues) {
                $countEl.hide();
                return;
            }

            if (!blockType) {
                var $block = $row.closest('.target-block');
                blockType = $block.data('blockType') || 'products';
            }

            // Check if this is a country selection - show holiday count instead
            var isCountrySelection = (searchEntity === 'countries' && valueType === 'entity_search');
            console.log('[updateConditionCount] isCountrySelection:', isCountrySelection, 'values:', values);

            $countEl.find('.preview-count').html(this.esIcon('progress_activity', 'es-spin'));
            $countEl.removeClass('clickable no-matches country-holidays').show();

            // For countries, fetch holiday count
            if (isCountrySelection && Array.isArray(values) && values.length > 0) {
                console.log('[updateConditionCount] Fetching holiday count for countries:', values);
                $countEl.data('conditionData', {
                    method: method,
                    values: values,
                    blockType: blockType,
                    isExclude: isExclude,
                    isCountryHolidays: true,
                    countryIds: values
                });

                $.ajax({
                    url: self.config.ajaxUrl,
                    type: 'POST',
                    dataType: 'json',
                    data: {
                        ajax: 1,
                        action: 'getHolidaysForCountries',
                        trait: 'EntitySelector',
                        country_ids: values.join(','),
                        count_only: 1
                    },
                    success: function(response) {
                        console.log('[updateConditionCount] Holiday response:', response);
                        if (response && response.success) {
                            var count = response.total_count || 0;
                            console.log('[updateConditionCount] Holiday count:', count);
                            $countEl.removeClass('no-matches clickable');
                            $countEl.addClass('country-holidays');
                            if (count === 0) {
                                $countEl.find('.preview-count').text(count);
                                $countEl.addClass('no-matches').show();
                            } else {
                                $countEl.find('.preview-count').text(count);
                                $countEl.addClass('clickable').show();
                            }
                            // Store countries info for popover
                            $countEl.data('countriesInfo', response.countries || []);
                        } else {
                            console.log('[updateConditionCount] Holiday response failed:', response);
                            $countEl.hide().removeClass('clickable');
                        }
                    },
                    error: function() {
                        $countEl.hide().removeClass('clickable');
                    }
                });
                return;
            }

            // Default: count entities
            $countEl.data('conditionData', {
                method: method,
                values: values,
                blockType: blockType,
                isExclude: isExclude
            });

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'countConditionMatches',
                    trait: 'EntitySelector',
                    method: method,
                    values: JSON.stringify(values),
                    block_type: blockType
                },
                success: function(response) {
                    if (response && response.success) {
                        var count = response.count || 0;
                        $countEl.removeClass('no-matches clickable');
                        if (count === 0) {
                            $countEl.find('.preview-count').text(count);
                            $countEl.addClass('no-matches').show();
                        } else {
                            $countEl.find('.preview-count').text(count);
                            $countEl.addClass('clickable').show();
                        }
                    } else {
                        $countEl.hide().removeClass('clickable');
                    }
                },
                error: function() {
                    $countEl.hide().removeClass('clickable');
                }
            });
        },

        updateGroupTotalCount: function($group) {
            var self = this;
            var $block = $group.closest('.target-block');
            var blockType = $block.data('blockType') || 'products';
            var $badge = $group.find('.group-header .group-count-badge');
            var $limitInput = $group.find('.group-modifier-limit');

            // Build group data for AJAX
            var groupData = this.serializeGroup($group, blockType);

            // Check if include has valid data
            if (!groupData.include || !groupData.include.method) {
                $badge.hide();
                $limitInput.attr('placeholder', '–');
                return;
            }

            // Show loading
            $badge.html(this.esIcon('progress_activity', 'es-spin')).show();

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'countGroupItems',
                    trait: 'EntitySelector',
                    group_data: JSON.stringify(groupData),
                    block_type: blockType
                },
                success: function(response) {
                    if (response && response.success) {
                        var finalCount = response.final_count || 0;
                        var excludeCount = response.exclude_count || 0;

                        // Update badge with eye icon and count
                        var badgeHtml = self.esIcon('visibility') + ' ' + finalCount;
                        if (excludeCount > 0) {
                            badgeHtml += ' <span class="exclude-info">(-' + excludeCount + ')</span>';
                        }
                        $badge.html(badgeHtml);
                        $badge.addClass('clickable').show();

                        // Store group data on badge for preview popover
                        $badge.data('groupData', groupData);
                        $badge.data('blockType', blockType);
                        $badge.data('finalCount', finalCount);

                        // Update limit placeholder with the count
                        $limitInput.attr('placeholder', finalCount);

                        // Also update the group-preview-badge count (apply limit if set)
                        var $previewBadge = $group.find('.group-preview-badge .preview-count');
                        if ($previewBadge.length) {
                            var limit = parseInt($limitInput.val(), 10);
                            var displayCount = (limit > 0 && limit < finalCount) ? limit : finalCount;
                            $previewBadge.text(displayCount);
                        }
                    } else {
                        $badge.hide().removeClass('clickable');
                        $limitInput.attr('placeholder', '–');
                    }
                },
                error: function() {
                    $badge.hide();
                    $limitInput.attr('placeholder', '–');
                }
            });
        },

        // Exclude row management
        addFirstExcludeRow: function($group, $block) {
            var $excludesDiv = $group.find('.group-excludes');
            var trans = this.config.trans || {};

            // Build the full excludes structure with first row
            var html = '<div class="except-separator">';
            html += '<span class="except-label">' + this.esIcon('block') + ' ' + (trans.except || 'EXCEPT') + '</span>';
            html += '</div>';

            html += '<div class="exclude-rows-container">';
            html += this.buildExcludeRowHtml($block, 0);
            html += '</div>';

            html += '<button type="button" class="btn-add-another-exclude">';
            html += this.esIcon('add') + ' ' + (trans.add_another_exception || 'Add another exception');
            html += '</button>';

            $excludesDiv.addClass('has-excludes').html(html);

            // Enhance the first exclude method select with styled dropdown
            var $firstRow = $excludesDiv.find('.exclude-row[data-exclude-index="0"]');
            var $firstSelect = $firstRow.find('.exclude-method-select');
            this.enhanceMethodSelect($firstSelect);

            // Update method info placeholder for initial selection
            var blockType = $block.data('blockType');
            var initialMethod = $firstSelect.val();
            this.updateMethodInfoPlaceholder($firstRow.find('.method-selector-wrapper'), initialMethod, blockType);

            this.updateMethodSelectorLock($group, true);
            this.serializeAllBlocks();
        },

        addExcludeRow: function($group, $block) {
            var $container = $group.find('.exclude-rows-container');

            // Get next exclude index
            var maxIndex = -1;
            $container.find('.exclude-row').each(function() {
                var idx = parseInt($(this).data('excludeIndex'), 10);
                if (idx > maxIndex) maxIndex = idx;
            });
            var excludeIndex = maxIndex + 1;

            var html = this.buildExcludeRowHtml($block, excludeIndex);
            $container.append(html);

            // Enhance the exclude method select with styled dropdown
            var $newRow = $container.find('.exclude-row[data-exclude-index="' + excludeIndex + '"]');
            var $newSelect = $newRow.find('.exclude-method-select');
            this.enhanceMethodSelect($newSelect);

            // Update method info placeholder for initial selection
            var blockType = $block.data('blockType');
            var initialMethod = $newSelect.val();
            this.updateMethodInfoPlaceholder($newRow.find('.method-selector-wrapper'), initialMethod, blockType);

            this.serializeAllBlocks();
        },

        buildExcludeRowHtml: function($block, excludeIndex) {
            var blockType = $block.data('blockType');
            var blockDef = this.config.blocks[blockType] || {};
            var methods = blockDef.selection_methods || {};
            var trans = this.config.trans || {};

            // Build exclude method options with optgroups (no "all")
            var excludeMethodOptions = this.buildMethodOptions(methods, true);

            // Find first non-all method for default search entity
            var firstSearchEntity = blockType;
            var firstValueType = 'entity_search';
            $.each(methods, function(methodKey, methodDef) {
                if (methodKey === 'all') return true;
                firstSearchEntity = methodDef.search_entity || blockType;
                firstValueType = methodDef.value_type || 'entity_search';
                return false; // break
            });

            var html = '<div class="exclude-row" data-exclude-index="' + excludeIndex + '">';

            // Header row with method select wrapped in method-selector-wrapper (same as include)
            html += '<div class="exclude-header-row">';
            html += '<div class="method-selector-wrapper">';
            html += '<select class="exclude-method-select">' + excludeMethodOptions + '</select>';
            html += '<span class="condition-match-count no-matches">' + this.esIcon('visibility') + ' <span class="preview-count">0</span></span>';
            html += '<span class="method-info-placeholder"></span>';
            html += '</div>';
            html += '<button type="button" class="btn-remove-exclude-row" title="' + (trans.remove_this_exception || 'Remove this exception') + '">';
            html += this.esIcon('delete');
            html += '</button>';
            html += '</div>';

            // Value picker based on first method's value type
            html += this.buildValuePickerHtml('exclude', firstValueType, firstSearchEntity, methods);

            html += '</div>';

            return html;
        },

        removeExcludeRow: function($excludeRow, $group, $block) {
            var $container = $group.find('.exclude-rows-container');
            var trans = this.config.trans || {};

            $excludeRow.remove();

            // Check if there are remaining exclude rows
            var remainingRows = $container.find('.exclude-row').length;

            if (remainingRows === 0) {
                // Remove entire excludes section and show "Add exceptions" button
                var $excludesDiv = $group.find('.group-excludes');
                $excludesDiv.removeClass('has-excludes').html(
                    '<button type="button" class="btn-add-exclude">' +
                    this.esIcon('add') + ' ' + (trans.add_exceptions || 'Add exceptions') +
                    '</button>'
                );
                // Unlock the method selector since no excludes exist
                this.updateMethodSelectorLock($group, false);
            }

            this.serializeAllBlocks();
        },

        // Method options building
        buildMethodOptions: function(methods, excludeAll) {
            var self = this;
            var trans = this.config.trans || {};
            var html = '';

            // Group labels
            var groupLabels = {
                'select_by': trans.select_by || 'Select by...',
                'filter_by': trans.filter_by || 'Filter by...'
            };

            // Separate methods by group
            var grouped = {};
            var ungrouped = {};

            $.each(methods, function(methodKey, methodDef) {
                if (excludeAll && methodKey === 'all') return true; // skip

                var group = methodDef.group || '';
                if (group) {
                    if (!grouped[group]) {
                        grouped[group] = {};
                    }
                    grouped[group][methodKey] = methodDef;
                } else {
                    ungrouped[methodKey] = methodDef;
                }
            });

            // Render ungrouped options first
            $.each(ungrouped, function(methodKey, methodDef) {
                html += self.buildMethodOption(methodKey, methodDef);
            });

            // Render grouped options with optgroups
            $.each(grouped, function(groupKey, groupMethods) {
                var groupLabel = groupLabels[groupKey] || groupKey.replace(/_/g, ' ');
                html += '<optgroup label="' + self.escapeAttr(groupLabel) + '">';
                $.each(groupMethods, function(methodKey, methodDef) {
                    html += self.buildMethodOption(methodKey, methodDef);
                });
                html += '</optgroup>';
            });

            return html;
        },

        buildMethodOption: function(methodKey, methodDef) {
            var html = '<option value="' + this.escapeAttr(methodKey) + '"';
            html += ' data-value-type="' + this.escapeAttr(methodDef.value_type || 'none') + '"';

            if (methodDef.icon) {
                html += ' data-icon="' + this.escapeAttr(methodDef.icon) + '"';
            }
            if (methodDef.search_entity) {
                html += ' data-search-entity="' + this.escapeAttr(methodDef.search_entity) + '"';
            }
            if (methodDef.options) {
                html += ' data-options="' + this.escapeAttr(JSON.stringify(methodDef.options)) + '"';
            }
            if (methodDef.exclusive) {
                html += ' data-exclusive="true"';
            }
            if (typeof methodDef.step !== 'undefined') {
                html += ' data-step="' + this.escapeAttr(methodDef.step) + '"';
            }
            if (typeof methodDef.min !== 'undefined') {
                html += ' data-min="' + this.escapeAttr(methodDef.min) + '"';
            }

            html += '>' + this.escapeHtml(methodDef.label) + '</option>';
            return html;
        },

        buildValuePickerHtml: function(section, valueType, searchEntity, methods) {
            var trans = this.config.trans || {};
            var pickerClass = section + '-picker';
            var chipsClass = section + '-chips';
            var dataClass = section + '-values-data';
            var html = '';

            if (valueType === 'none') {
                html = '<div class="value-picker ' + pickerClass + '" style="display:none;" data-search-entity="" data-value-type="none">';
                html += '<input type="hidden" class="' + dataClass + '" value="[]">';
                html += '</div>';
                return html;
            }

            html = '<div class="value-picker ' + pickerClass + '" data-search-entity="' + this.escapeAttr(searchEntity) + '" data-value-type="' + this.escapeAttr(valueType) + '">';

            switch (valueType) {
                case 'entity_search':
                    var noItemsText = trans.no_items_selected || 'No items selected - use search below';
                    html += '<div class="entity-chips ' + chipsClass + '" data-placeholder="' + this.escapeAttr(noItemsText) + '"></div>';
                    html += '<div class="entity-search-box">';
                    html += this.esIcon('search', 'entity-search-icon');
                    html += '<input type="text" class="entity-search-input" placeholder="' + this.escapeAttr(trans.search_placeholder || 'Search by name, reference, ID...') + '" autocomplete="off">';
                    html += '<span class="search-loading" style="display:none;">' + this.esIcon('progress_activity', 'es-spin') + '</span>';
                    html += '</div>';
                    html += '<input type="hidden" class="' + dataClass + '" value="[]">';
                    break;

                case 'pattern':
                    // Build tooltip content for data-details attribute
                    var tooltipContent = '<strong>' + this.escapeHtml(trans.pattern_help_title || 'Pattern Syntax') + '</strong>';
                    tooltipContent += '<div class="pattern-help-content">';
                    tooltipContent += '<div class="pattern-help-item"><code>*</code> <span>' + this.escapeHtml(trans.pattern_help_wildcard || 'any text (wildcard)') + '</span></div>';
                    tooltipContent += '<div class="pattern-help-item"><code>{number}</code> <span>' + this.escapeHtml(trans.pattern_help_number || 'any number (e.g. 100, 250)') + '</span></div>';
                    tooltipContent += '<div class="pattern-help-item"><code>{letter}</code> <span>' + this.escapeHtml(trans.pattern_help_letter || 'single letter (A-Z)') + '</span></div>';
                    tooltipContent += '</div>';
                    tooltipContent += '<div class="pattern-help-examples">';
                    tooltipContent += '<strong>' + this.escapeHtml(trans.pattern_help_examples || 'Examples:') + '</strong>';
                    tooltipContent += '<div class="pattern-example"><code>*cotton*</code> <span>' + this.escapeHtml(trans.pattern_example_1 || 'contains "cotton"') + '</span></div>';
                    tooltipContent += '<div class="pattern-example"><code>iPhone {number} Pro*</code> <span>' + this.escapeHtml(trans.pattern_example_2 || 'matches "iPhone 15 Pro Max"') + '</span></div>';
                    tooltipContent += '<div class="pattern-example"><code>Size {letter}</code> <span>' + this.escapeHtml(trans.pattern_example_3 || 'matches "Size M", "Size L"') + '</span></div>';
                    tooltipContent += '</div>';

                    var noPatternText = trans.no_patterns || 'No patterns - press Enter to add';
                    html += '<div class="entity-chips pattern-chips" data-placeholder="' + this.escapeAttr(noPatternText) + '"></div>';
                    html += '<div class="pattern-input-row">';
                    // Draft tag styled exactly like saved tags, with input instead of text span
                    html += '<div class="pattern-tag draft-tag" data-case-sensitive="0">';
                    html += '<button type="button" class="btn-toggle-case" title="' + this.escapeAttr(trans.case_insensitive || 'Case insensitive - click to toggle') + '"><span class="case-icon">aa</span></button>';
                    html += '<input type="text" class="pattern-input" value="" placeholder="' + this.escapeAttr(trans.enter_pattern || 'e.g. *cotton*') + '">';
                    html += '<span class="pattern-match-count" title="' + this.escapeAttr(trans.click_to_preview || 'Click to preview matches') + '">' + this.esIcon('visibility') + ' <span class="count-value"></span></span>';
                    html += '<button type="button" class="btn-add-pattern" title="' + this.escapeAttr(trans.add_pattern || 'Add pattern (Enter)') + '">' + this.esIcon('add') + '</button>';
                    html += '</div>';
                    html += '<span class="mpr-info-wrapper" data-details="' + this.escapeAttr(tooltipContent) + '">';
                    html += this.esIcon('info');
                    html += '</span>';
                    html += '</div>';
                    html += '<input type="hidden" class="' + dataClass + '" value="[]">';
                    break;

                case 'numeric_range':
                    html += '<div class="numeric-range-box">';
                    html += '<input type="number" class="range-min-input" value="" placeholder="' + this.escapeAttr(trans.min || 'Min') + '" step="0.01">';
                    html += '<span class="range-separator">-</span>';
                    html += '<input type="number" class="range-max-input" value="" placeholder="' + this.escapeAttr(trans.max || 'Max') + '" step="0.01">';
                    html += '</div>';
                    html += '<input type="hidden" class="' + dataClass + '" value="[]">';
                    break;

                case 'multi_numeric_range':
                    html += '<div class="multi-range-container">';
                    html += '<div class="multi-range-chips"></div>';
                    html += '<div class="multi-range-input-row">';
                    html += '<input type="number" class="range-min-input" value="" placeholder="' + this.escapeAttr(trans.min || 'Min') + '" step="0.01">';
                    html += '<span class="range-separator">-</span>';
                    html += '<input type="number" class="range-max-input" value="" placeholder="' + this.escapeAttr(trans.max || 'Max') + '" step="0.01">';
                    html += '<button type="button" class="btn-add-range" title="' + this.escapeAttr(trans.add_range || 'Add range') + '">' + this.esIcon('add') + '</button>';
                    html += '</div>';
                    html += '</div>';
                    html += '<input type="hidden" class="' + dataClass + '" value="[]">';
                    break;

                case 'multi_select_tiles':
                    html += '<div class="multi-select-tiles">';
                    // Tiles will be populated based on method options
                    html += '</div>';
                    html += '<input type="hidden" class="' + dataClass + '" value="[]">';
                    break;

                case 'date_range':
                    html += '<div class="date-range-box">';
                    html += '<input type="date" class="date-from-input" value="">';
                    html += '<span class="range-separator">-</span>';
                    html += '<input type="date" class="date-to-input" value="">';
                    html += '</div>';
                    html += '<input type="hidden" class="' + dataClass + '" value="[]">';
                    break;

                case 'select':
                    html += '<div class="select-input-box">';
                    html += '<select class="select-value-input"></select>';
                    html += '</div>';
                    html += '<input type="hidden" class="' + dataClass + '" value="[]">';
                    break;

                case 'boolean':
                    html += '<div class="boolean-input-box">';
                    html += '<span class="boolean-label">' + this.escapeHtml(trans.yes || 'Yes') + '</span>';
                    html += '</div>';
                    html += '<input type="hidden" class="' + dataClass + '" value="[true]">';
                    break;

                case 'combination_attributes':
                    // Build tooltip content
                    var combTooltip = '<strong>' + this.escapeHtml(trans.combination_help_title || 'Combination Targeting') + '</strong>';
                    combTooltip += '<div class="combination-help-content">';
                    combTooltip += '<p>' + this.escapeHtml(trans.combination_help_desc || 'Select attributes to target specific product combinations.') + '</p>';
                    combTooltip += '<p><strong>' + this.escapeHtml(trans.combination_help_logic || 'Logic:') + '</strong></p>';
                    combTooltip += '<ul>';
                    combTooltip += '<li>' + this.escapeHtml(trans.combination_help_within || 'Within group: OR (Red OR Blue)') + '</li>';
                    combTooltip += '<li>' + this.escapeHtml(trans.combination_help_between || 'Between groups: AND (Color AND Size)') + '</li>';
                    combTooltip += '</ul>';
                    combTooltip += '</div>';

                    // Combination mode from config: 'products', 'combinations', or 'toggle'
                    var combMode = this.config.combinationMode || 'products';
                    var showModeToggle = (combMode === 'toggle');
                    var defaultMode = showModeToggle ? 'products' : combMode;

                    html += '<div class="combination-attributes-picker" data-combination-mode="' + this.escapeAttr(combMode) + '">';
                    // Mode toggle: only show when config is 'toggle'
                    if (showModeToggle) {
                        html += '<div class="combination-mode-toggle">';
                        html += '<label class="combination-mode-option">';
                        html += '<input type="radio" name="' + this.escapeAttr(section) + '_comb_mode_' + Date.now() + '" class="comb-mode-radio" value="products" checked>';
                        html += '<span class="mode-label">' + this.escapeHtml(trans.comb_mode_products || 'Products with these combinations') + '</span>';
                        html += '</label>';
                        html += '<label class="combination-mode-option">';
                        html += '<input type="radio" name="' + this.escapeAttr(section) + '_comb_mode_' + Date.now() + '" class="comb-mode-radio" value="combinations">';
                        html += '<span class="mode-label">' + this.escapeHtml(trans.comb_mode_combinations || 'Only these exact combinations') + '</span>';
                        html += '</label>';
                        html += '</div>';
                    }
                    html += '<div class="combination-groups-container">';
                    html += '<span class="combination-loading">' + this.esIcon('progress_activity', 'es-spin') + ' ' + this.escapeHtml(trans.loading || 'Loading...') + '</span>';
                    html += '</div>';
                    html += '</div>';
                    // Store mode along with attributes: { mode: 'products'|'combinations', attributes: { groupId: [valueIds] } }
                    html += '<input type="hidden" class="' + dataClass + '" value=\'{"mode":"' + defaultMode + '","attributes":{}}\'>';
                    break;

                default:
                    html += '<input type="hidden" class="' + dataClass + '" value="[]">';
                    break;
            }

            html += '</div>';
            return html;
        },

        // Sort options
        getSortOptionsArray: function(blockType) {
            var trans = this.config.trans || {};

            switch (blockType) {
                case 'products':
                    return [
                        { value: 'sales', label: trans.sort_bestsellers || 'Best sellers' },
                        { value: 'date_add', label: trans.sort_newest || 'Newest' },
                        { value: 'price', label: trans.sort_price || 'Price' },
                        { value: 'name', label: trans.sort_name || 'Name' },
                        { value: 'position', label: trans.sort_position || 'Position' },
                        { value: 'quantity', label: trans.sort_stock || 'Stock quantity' },
                        { value: 'random', label: trans.sort_random || 'Random' }
                    ];
                case 'categories':
                    return [
                        { value: 'name', label: trans.sort_name || 'Name' },
                        { value: 'position', label: trans.sort_position || 'Position' },
                        { value: 'product_count', label: trans.sort_products || 'Products count' },
                        { value: 'date_add', label: trans.sort_newest || 'Newest' }
                    ];
                default:
                    return [
                        { value: 'name', label: trans.sort_name || 'Name' },
                        { value: 'date_add', label: trans.sort_newest || 'Newest' }
                    ];
            }
        },

        getSortIconName: function(sortBy, sortDir) {
            switch (sortBy) {
                case 'name':
                    return 'sort_by_alpha';
                case 'random':
                    return 'shuffle';
                default:
                    return 'sort';
            }
        },

        cycleSortOption: function($btn, blockType) {
            var sortOptions = this.getSortOptionsArray(blockType);
            var currentSort = $btn.data('sort') || 'sales';
            var currentDir = $btn.data('dir') || 'DESC';

            // Find current index
            var currentIndex = -1;
            for (var i = 0; i < sortOptions.length; i++) {
                if (sortOptions[i].value === currentSort) {
                    currentIndex = i;
                    break;
                }
            }

            // Cycle: first toggle direction, then move to next sort option
            var newSort, newDir, newLabel;
            if (currentDir === 'DESC') {
                // Toggle to ASC, same sort
                newSort = currentSort;
                newDir = 'ASC';
            } else {
                // Move to next sort option, reset to DESC
                var nextIndex = (currentIndex + 1) % sortOptions.length;
                newSort = sortOptions[nextIndex].value;
                newDir = 'DESC';
            }

            // Find label for new sort
            for (var j = 0; j < sortOptions.length; j++) {
                if (sortOptions[j].value === newSort) {
                    newLabel = sortOptions[j].label;
                    break;
                }
            }

            // Update button
            $btn.data('sort', newSort);
            $btn.data('dir', newDir);
            $btn.attr('data-sort', newSort);
            $btn.attr('data-dir', newDir);
            $btn.attr('title', newLabel + ' ' + (newDir === 'DESC' ? '↓' : '↑'));
            $btn.find('i').replaceWith(this.esIcon(this.getSortIconName(newSort, newDir)));
        },

        // Validation
        validate: function() {
            var isRequired = this.$wrapper.data('required') === 1 || this.$wrapper.data('required') === '1';
            if (!isRequired) {
                return true;
            }

            // Check if any block has data (groups with selections)
            var hasData = false;
            this.$wrapper.find('.target-block').each(function() {
                if ($(this).find('.selection-group').length > 0) {
                    hasData = true;
                    return false; // break
                }
            });

            if (!hasData) {
                // Show validation error
                this.showValidationError();
                return false;
            }

            // Valid - remove any previous error
            this.clearValidationError();
            return true;
        },

        showValidationError: function() {
            this.$wrapper.addClass('has-validation-error');
            var message = this.$wrapper.data('required-message') || 'Please select at least one item';

            // Remove any existing error
            this.$wrapper.find('.trait-validation-error').remove();

            // Add error message after header
            var $error = $('<div>', {
                class: 'trait-validation-error',
                html: this.esIcon('warning') + ' ' + message
            });
            this.$wrapper.find('.condition-trait-header').after($error);

            // Scroll to error
            $('html, body').animate({
                scrollTop: this.$wrapper.offset().top - 100
            }, 300);

            // Expand the trait if collapsed
            if (!this.$wrapper.find('.condition-trait-body').is(':visible')) {
                this.$wrapper.find('.condition-trait-body').slideDown(200);
                this.$wrapper.removeClass('collapsed');
            }
        },

        clearValidationError: function() {
            this.$wrapper.removeClass('has-validation-error');
            this.$wrapper.find('.trait-validation-error').remove();
        }
    };

})(jQuery);

/**
 * Entity Selector - Methods Module
 * Method dropdown rendering, value pickers, combination picker
 * @partial _methods.js
 *
 * EXTRACTION SOURCE: assets/js/admin/entity-selector.js
 * Lines: 6760-6848 (initMethodDropdowns, enhanceMethodSelect)
 *        6849-7051 (showMethodDropdownMenu, buildMethodDropdownMenuHtml, closeMethodDropdownMenu)
 *        7053-7138 (populateTiles, applyRangeInputConstraints, showRangeInputError)
 *        7139-7380 (combination picker methods)
 *        7382-7550 (updateMethodInfoPlaceholder, getBuiltInMethodHelp)
 *        7748-7888 (buildSortOptions, updateModifierButtonState, updateMethodSelectorLock)
 *
 * Contains:
 * - initMethodDropdowns() - Initialize styled dropdowns
 * - enhanceMethodSelect() - Convert select to styled dropdown
 * - showMethodDropdownMenu() - Show method selection menu
 * - buildMethodDropdownMenuHtml() - Build menu HTML
 * - closeMethodDropdownMenu() - Close dropdown menu
 * - updateMethodTrigger() - Update trigger display
 * - populateTiles() - Build multi-select tiles
 * - applyRangeInputConstraints() - Set numeric input constraints
 * - showRangeInputError() - Display validation error
 * - loadCombinationAttributeGroups() - Load attribute groups for picker
 * - loadCombinationAttributeValues() - Load values for attribute group
 * - restoreCombinationSelections() - Restore saved combination state
 * - updateCombinationData() - Save combination selection
 * - updateCombinationGroupCounts() - Update selection counts
 * - updateMethodInfoPlaceholder() - Show method help
 * - getBuiltInMethodHelp() - Get help text for methods
 * - buildSortOptions() - Build sort dropdown options
 * - updateModifierButtonState() - Update modifier toggle state
 * - updateMethodSelectorLock() - Lock/unlock method selector
 */

(function($) {
    'use strict';

    window._EntitySelectorMixins = window._EntitySelectorMixins || {};

    window._EntitySelectorMixins.methods = {

        /**
         * Initialize styled method dropdowns for all method selects
         */
        initMethodDropdowns: function() {
            var self = this;
            this.$wrapper.find('.include-method-select').each(function() {
                self.enhanceMethodSelect($(this));
            });
            this.$wrapper.find('.exclude-method-select').each(function() {
                self.enhanceMethodSelect($(this));
            });
            this.initMethodInfoPlaceholders();
        },

        /**
         * Initialize info placeholders for all existing method selects
         */
        initMethodInfoPlaceholders: function() {
            var self = this;
            this.$wrapper.find('.selection-group').each(function() {
                var $group = $(this);
                var $block = $group.closest('.target-block');
                var blockType = $block.data('blockType') || 'products';

                // Include method info
                var includeMethod = $group.find('.include-method-select').val() || 'all';
                self.updateMethodInfoPlaceholder($group.find('.method-selector-wrapper'), includeMethod, blockType);

                // Exclude methods info
                $group.find('.exclude-row').each(function() {
                    var $row = $(this);
                    var excludeMethod = $row.find('.exclude-method-select').val();
                    if (excludeMethod) {
                        self.updateMethodInfoPlaceholder($row.find('.method-selector-wrapper'), excludeMethod, blockType);
                    }
                });
            });
        },

        /**
         * Enhance a single method select with styled dropdown
         */
        enhanceMethodSelect: function($select) {
            var self = this;

            if (!$select.length || $select.data('methodDropdownInit')) {
                return;
            }
            $select.data('methodDropdownInit', true);

            $select.addClass('method-select-hidden');

            var $selectedOption = $select.find('option:selected');
            var selectedIcon = $selectedOption.data('icon') || 'arrow_drop_down';
            var selectedLabel = $selectedOption.text();

            var triggerHtml = '<div class="method-dropdown-trigger">';
            triggerHtml += this.esIcon(selectedIcon, 'method-trigger-icon');
            triggerHtml += '<span class="method-trigger-label">' + this.escapeHtml(selectedLabel) + '</span>';
            triggerHtml += this.esIcon('arrow_drop_down', 'method-trigger-caret');
            triggerHtml += '</div>';

            var $trigger = $(triggerHtml);
            $select.after($trigger);

            $trigger.on('click', function(e) {
                e.preventDefault();
                e.stopPropagation();

                var $wrapper = $select.closest('.method-selector-wrapper');
                if ($wrapper.hasClass('selector-locked')) {
                    return;
                }

                self.showMethodDropdownMenu($select, $trigger);
            });

            $select.on('change.methodDropdown', function() {
                self.updateMethodTrigger($select, $trigger);
            });
        },

        /**
         * Update the trigger display to match current selection
         */
        updateMethodTrigger: function($select, $trigger) {
            var $selectedOption = $select.find('option:selected');
            var selectedIcon = $selectedOption.data('icon') || 'arrow_drop_down';
            var selectedLabel = $selectedOption.text();

            $trigger.find('.method-trigger-icon').replaceWith(this.esIcon(selectedIcon, 'method-trigger-icon'));
            $trigger.find('.method-trigger-label').text(selectedLabel);
        },

        /**
         * Show the method dropdown menu
         */
        showMethodDropdownMenu: function($select, $trigger) {
            var self = this;

            this.closeMethodDropdownMenu();

            var menuHtml = this.buildMethodDropdownMenuHtml($select);
            var $menu = $(menuHtml);

            var triggerOffset = $trigger.offset();
            var triggerWidth = $trigger.outerWidth();
            var triggerHeight = $trigger.outerHeight();

            $menu.css({
                position: 'absolute',
                top: triggerOffset.top + triggerHeight + 2,
                left: triggerOffset.left,
                minWidth: triggerWidth,
                zIndex: 10001
            });

            $('body').append($menu);
            this.$methodDropdownMenu = $menu;
            this.$methodDropdownSelect = $select;
            this.$methodDropdownTrigger = $trigger;

            $menu.on('click', '.method-dropdown-item', function(e) {
                e.preventDefault();
                e.stopPropagation();

                var value = $(this).data('value');
                $select.val(value).trigger('change');
                self.closeMethodDropdownMenu();
            });

            $(document).on('click.methodDropdown', function(e) {
                if (!$(e.target).closest('.method-dropdown-menu, .method-dropdown-trigger').length) {
                    self.closeMethodDropdownMenu();
                }
            });

            $(document).on('keydown.methodDropdown', function(e) {
                if (e.keyCode === 27) {
                    self.closeMethodDropdownMenu();
                }
            });
        },

        /**
         * Build the dropdown menu HTML
         */
        buildMethodDropdownMenuHtml: function($select) {
            var self = this;
            var html = '<div class="method-dropdown-menu">';

            // Render ungrouped options first
            $select.children('option').each(function() {
                var $el = $(this);
                var icon = $el.data('icon') || 'star';
                var label = $el.text();
                var value = $el.val();
                var isSelected = $el.is(':selected');

                html += '<div class="method-dropdown-item' + (isSelected ? ' selected' : '') + '" data-value="' + self.escapeAttr(value) + '">';
                html += this.esIcon(icon, 'method-item-icon');
                html += '<span class="method-item-label">' + self.escapeHtml(label) + '</span>';
                if (isSelected) {
                    html += self.esIcon('check', 'method-item-check');
                }
                html += '</div>';
            });

            // Render optgroups
            $select.children('optgroup').each(function() {
                var $optgroup = $(this);
                var groupLabel = $optgroup.attr('label') || '';

                html += '<div class="method-dropdown-optgroup">';
                html += '<div class="method-optgroup-label">' + self.escapeHtml(groupLabel) + '</div>';
                html += '<div class="method-optgroup-items">';

                $optgroup.children('option').each(function() {
                    var $el = $(this);
                    var icon = $el.data('icon') || 'settings';
                    var label = $el.text();
                    var value = $el.val();
                    var isSelected = $el.is(':selected');

                    html += '<div class="method-dropdown-item' + (isSelected ? ' selected' : '') + '" data-value="' + self.escapeAttr(value) + '">';
                    html += self.esIcon(icon, 'method-item-icon');
                    html += '<span class="method-item-label">' + self.escapeHtml(label) + '</span>';
                    if (isSelected) {
                        html += self.esIcon('check', 'method-item-check');
                    }
                    html += '</div>';
                });

                html += '</div>'; // close items
                html += '</div>'; // close optgroup
            });

            html += '</div>';
            return html;
        },

        /**
         * Close the method dropdown menu
         */
        closeMethodDropdownMenu: function() {
            if (this.$methodDropdownMenu) {
                this.$methodDropdownMenu.remove();
                this.$methodDropdownMenu = null;
            }
            this.$methodDropdownSelect = null;
            this.$methodDropdownTrigger = null;
            $(document).off('click.methodDropdown keydown.methodDropdown');
        },

        /**
         * Populate tiles for multi_select_tiles value picker
         */
        populateTiles: function($picker, options, exclusive) {
            var self = this;
            var $container = $picker.find('.multi-select-tiles');
            $container.empty();

            if (exclusive) {
                $container.attr('data-exclusive', 'true');
            } else {
                $container.removeAttr('data-exclusive');
            }

            $.each(options, function(key, optData) {
                var label = typeof optData === 'object' ? optData.label : optData;
                var icon = typeof optData === 'object' && optData.icon ? optData.icon : null;
                var color = typeof optData === 'object' && optData.color ? optData.color : null;

                var tileClass = 'tile-option';
                if (color) {
                    tileClass += ' tile-color-' + color;
                }

                var $tile = $('<button>', {
                    type: 'button',
                    class: tileClass,
                    'data-value': key
                });

                if (icon) {
                    $tile.append($('<i>', { class: icon }));
                }
                $tile.append($('<span>', { class: 'tile-label', text: label }));

                $container.append($tile);
            });
        },

        /**
         * Apply step/min constraints to numeric range inputs
         */
        applyRangeInputConstraints: function($picker, step, min) {
            var $inputs = $picker.find('.range-min-input, .range-max-input');

            if (typeof step !== 'undefined' && step !== null) {
                $inputs.attr('step', step);
            } else {
                $inputs.attr('step', 'any');
            }

            if (typeof min !== 'undefined' && min !== null) {
                $inputs.attr('min', min);
            } else {
                $inputs.removeAttr('min');
            }
        },

        /**
         * Show error message on range input
         */
        showRangeInputError: function($input, message) {
            var $container = $input.closest('.multi-range-input-row');

            $container.find('.range-input-error').remove();
            $container.find('.range-min-input, .range-max-input').removeClass('has-error');

            $input.addClass('has-error');
            var $error = $('<span>', {
                class: 'range-input-error',
                text: message
            });
            $container.append($error);

            setTimeout(function() {
                $input.removeClass('has-error');
                $error.fadeOut(200, function() {
                    $(this).remove();
                });
            }, 3000);
        },

        /**
         * Load attribute groups for combination picker
         */
        loadCombinationAttributeGroups: function($picker) {
            var self = this;
            var trans = this.config.trans || {};
            var $container = $picker.find('.combination-groups-container');

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'getAttributeGroups',
                    trait: 'TargetConditions'
                },
                success: function(response) {
                    $container.empty();

                    if (!response.success || !response.groups || response.groups.length === 0) {
                        $container.html('<span class="combination-empty">' +
                            self.escapeHtml(trans.no_attribute_groups || 'No attribute groups found') +
                            '</span>');
                        return;
                    }

                    response.groups.forEach(function(group) {
                        var $groupDiv = $('<div>', {
                            class: 'comb-attr-group',
                            'data-group-id': group.id
                        });

                        var $groupHeader = $('<div>', { class: 'comb-attr-group-header' });
                        $groupHeader.append($('<span>', {
                            class: 'comb-attr-group-name',
                            text: group.name
                        }));
                        $groupHeader.append($('<span>', {
                            class: 'comb-attr-group-count',
                            text: '0'
                        }));

                        var $toolbar = $('<div>', { class: 'comb-attr-toolbar' });
                        $toolbar.append($('<button>', {
                            type: 'button',
                            class: 'comb-toolbar-btn comb-select-all',
                            title: trans.select_all || 'Select all',
                            html: self.esIcon('check_box')
                        }));
                        $toolbar.append($('<button>', {
                            type: 'button',
                            class: 'comb-toolbar-btn comb-select-none',
                            title: trans.clear || 'Clear',
                            html: self.esIcon('check_box_outline_blank')
                        }));
                        $toolbar.append($('<input>', {
                            type: 'text',
                            class: 'comb-attr-search',
                            placeholder: trans.filter_results || 'Filter...'
                        }));

                        var $valuesContainer = $('<div>', {
                            class: 'comb-attr-values',
                            'data-loaded': 'false'
                        });
                        $valuesContainer.append($('<span>', {
                            class: 'comb-attr-loading',
                            html: self.esIcon('progress_activity', 'es-spin')
                        }));

                        $groupDiv.append($groupHeader);
                        $groupDiv.append($toolbar);
                        $groupDiv.append($valuesContainer);
                        $container.append($groupDiv);

                        self.loadCombinationAttributeValues($picker, group.id, $valuesContainer);
                    });
                },
                error: function() {
                    $container.html('<span class="combination-error">' +
                        self.escapeHtml(trans.error_loading || 'Error loading attribute groups') +
                        '</span>');
                }
            });
        },

        /**
         * Load attribute values for a specific group
         */
        loadCombinationAttributeValues: function($picker, groupId, $container) {
            var self = this;
            var trans = this.config.trans || {};

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'getAttributeValues',
                    trait: 'TargetConditions',
                    id_attribute_group: groupId
                },
                success: function(response) {
                    $container.empty();
                    $container.attr('data-loaded', 'true');

                    if (!response.success || !response.values || response.values.length === 0) {
                        $container.html('<span class="comb-attr-empty">' +
                            self.escapeHtml(trans.no_values || 'No values') +
                            '</span>');
                        return;
                    }

                    response.values.forEach(function(value) {
                        var productCount = parseInt(value.product_count) || 0;
                        var $valueBtn = $('<button>', {
                            type: 'button',
                            class: 'comb-attr-value',
                            'data-value-id': value.id,
                            'data-group-id': groupId,
                            'data-name': value.name.toLowerCase()
                        });
                        $valueBtn.append($('<span>', {
                            class: 'comb-attr-value-name',
                            text: value.name
                        }));
                        if (productCount > 0) {
                            $valueBtn.append($('<span>', {
                                class: 'comb-attr-value-count',
                                text: productCount
                            }));
                        }
                        $container.append($valueBtn);
                    });

                    self.restoreCombinationSelections($picker);
                },
                error: function() {
                    $container.html('<span class="comb-attr-error">' +
                        self.escapeHtml(trans.error_loading || 'Error') +
                        '</span>');
                }
            });
        },

        /**
         * Restore previously selected combination values from hidden input
         */
        restoreCombinationSelections: function($picker) {
            var $dataInput = $picker.find('.include-values-data, .exclude-values-data').first();
            var dataStr = $dataInput.val() || '{}';
            var data;

            try {
                data = JSON.parse(dataStr);
            } catch (e) {
                return;
            }

            var attributes = data.attributes || data;
            var mode = data.mode || 'products';

            $picker.find('.comb-mode-radio[value="' + mode + '"]').prop('checked', true);

            $.each(attributes, function(groupId, valueIds) {
                if (!Array.isArray(valueIds)) return;

                valueIds.forEach(function(valueId) {
                    $picker.find('.comb-attr-value[data-group-id="' + groupId + '"][data-value-id="' + valueId + '"]')
                        .addClass('selected');
                });
            });

            this.updateCombinationGroupCounts($picker);
        },

        /**
         * Update hidden input with current combination selections
         */
        updateCombinationData: function($picker) {
            var attributes = {};

            $picker.find('.comb-attr-value.selected').each(function() {
                var groupId = $(this).data('groupId').toString();
                var valueId = $(this).data('valueId');

                if (!attributes[groupId]) {
                    attributes[groupId] = [];
                }
                attributes[groupId].push(valueId);
            });

            var $combPicker = $picker.find('.combination-attributes-picker');
            var configMode = $combPicker.data('combinationMode') || this.config.combinationMode || 'products';
            var mode;

            if (configMode === 'toggle') {
                mode = $picker.find('.comb-mode-radio:checked').val() || 'products';
            } else {
                mode = configMode;
            }

            var data = {
                mode: mode,
                attributes: attributes
            };

            var $dataInput = $picker.find('.include-values-data, .exclude-values-data').first();
            $dataInput.val(JSON.stringify(data));

            this.updateCombinationGroupCounts($picker);
        },

        /**
         * Update the count badges on each attribute group
         */
        updateCombinationGroupCounts: function($picker) {
            $picker.find('.comb-attr-group').each(function() {
                var $group = $(this);
                var count = $group.find('.comb-attr-value.selected').length;
                $group.find('.comb-attr-group-count').text(count);

                if (count > 0) {
                    $group.addClass('has-selections');
                } else {
                    $group.removeClass('has-selections');
                }
            });
        },

        /**
         * Update the info placeholder based on method and block type
         */
        updateMethodInfoPlaceholder: function($headerRow, method, blockType) {
            var $placeholder = $headerRow.find('.method-info-placeholder');
            if (!$placeholder.length) return;

            $placeholder.empty();

            var methodHelp = this.config.methodHelp || {};
            var blockHelp = methodHelp[blockType] || methodHelp['products'] || {};
            var helpContent = blockHelp[method] || this.getBuiltInMethodHelp(method);

            if (helpContent) {
                var $infoWrapper = $('<span>', {
                    class: 'mpr-info-wrapper',
                    'data-details': helpContent
                });
                $infoWrapper.append($(this.esIcon('info')));
                $placeholder.append($infoWrapper);

                // Let prestashop-admin info-tooltip.js handle this element
                if (window.MPRInfoTooltip) {
                    window.MPRInfoTooltip.init();
                }
            }
        },

        /**
         * Get built-in help content for targeting methods
         */
        getBuiltInMethodHelp: function(method) {
            var trans = this.config.trans || {};
            var html = '';

            switch (method) {
                case 'all':
                    html = '<strong>' + this.escapeHtml(trans.help_all_title || 'All Items') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_all_desc || 'Selects all items without any filtering.') + '</p>';
                    break;

                case 'specific':
                    html = '<strong>' + this.escapeHtml(trans.help_specific_title || 'Specific Items') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_specific_desc || 'Search and select individual items by name, reference, or ID.') + '</p>';
                    break;

                case 'by_category':
                    html = '<strong>' + this.escapeHtml(trans.help_category_title || 'By Category') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_category_desc || 'Select items belonging to specific categories. Includes subcategories.') + '</p>';
                    break;

                case 'by_manufacturer':
                    html = '<strong>' + this.escapeHtml(trans.help_manufacturer_title || 'By Manufacturer') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_manufacturer_desc || 'Select items from specific manufacturers/brands.') + '</p>';
                    break;

                case 'by_supplier':
                    html = '<strong>' + this.escapeHtml(trans.help_supplier_title || 'By Supplier') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_supplier_desc || 'Select items from specific suppliers.') + '</p>';
                    break;

                case 'by_tag':
                    html = '<strong>' + this.escapeHtml(trans.help_tag_title || 'By Tag') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_tag_desc || 'Select items with specific tags assigned.') + '</p>';
                    break;

                case 'by_attribute':
                    html = '<strong>' + this.escapeHtml(trans.help_attribute_title || 'By Attribute') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_attribute_desc || 'Select items with specific attribute values (e.g., Color: Red).') + '</p>';
                    break;

                case 'by_feature':
                    html = '<strong>' + this.escapeHtml(trans.help_feature_title || 'By Feature') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_feature_desc || 'Select items with specific feature values (e.g., Material: Cotton).') + '</p>';
                    break;

                case 'by_combination':
                    html = '<strong>' + this.escapeHtml(trans.help_combination_title || 'Combination Targeting') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_combination_desc || 'Select items by combination attributes.') + '</p>';
                    html += '<p><strong>' + this.escapeHtml(trans.help_combination_logic || 'Logic:') + '</strong></p>';
                    html += '<ul>';
                    html += '<li>' + this.escapeHtml(trans.help_combination_within || 'Within group: OR (Red OR Blue)') + '</li>';
                    html += '<li>' + this.escapeHtml(trans.help_combination_between || 'Between groups: AND (Color AND Size)') + '</li>';
                    html += '</ul>';
                    break;

                case 'by_carrier':
                    html = '<strong>' + this.escapeHtml(trans.help_carrier_title || 'By Carrier') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_carrier_desc || 'Select items available with specific carriers.') + '</p>';
                    break;

                case 'by_condition':
                    html = '<strong>' + this.escapeHtml(trans.help_condition_title || 'By Condition') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_condition_desc || 'Filter by product condition: New, Used, or Refurbished.') + '</p>';
                    break;

                case 'by_visibility':
                    html = '<strong>' + this.escapeHtml(trans.help_visibility_title || 'By Visibility') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_visibility_desc || 'Filter by where products are visible in the store.') + '</p>';
                    break;

                case 'by_active_status':
                    html = '<strong>' + this.escapeHtml(trans.help_active_title || 'By Active Status') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_active_desc || 'Filter by whether products are enabled or disabled.') + '</p>';
                    break;

                case 'by_stock_status':
                    html = '<strong>' + this.escapeHtml(trans.help_stock_title || 'By Stock Status') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_stock_desc || 'Filter by stock availability: In stock, Out of stock, or Low stock.') + '</p>';
                    break;

                case 'by_on_sale':
                case 'by_has_specific_price':
                case 'by_is_virtual':
                case 'by_is_pack':
                case 'by_has_combinations':
                case 'by_available_for_order':
                case 'by_online_only':
                case 'by_has_related':
                case 'by_has_customization':
                case 'by_has_attachments':
                case 'by_has_additional_shipping':
                    html = '<strong>' + this.escapeHtml(trans.help_boolean_title || 'Yes/No Filter') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_boolean_desc || 'Filter products by this property.') + '</p>';
                    break;

                case 'by_name_pattern':
                case 'by_reference_pattern':
                case 'by_description_pattern':
                case 'by_long_description_pattern':
                case 'by_ean13_pattern':
                case 'by_upc_pattern':
                case 'by_isbn_pattern':
                case 'by_mpn_pattern':
                case 'by_meta_title_pattern':
                case 'by_meta_description_pattern':
                    html = '<strong>' + this.escapeHtml(trans.help_pattern_title || 'Pattern Matching') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_pattern_desc || 'Match text using patterns with wildcards.') + '</p>';
                    html += '<div><code>*</code> ' + this.escapeHtml(trans.help_pattern_wildcard || 'any text') + '</div>';
                    html += '<div><code>{number}</code> ' + this.escapeHtml(trans.help_pattern_number || 'any number') + '</div>';
                    html += '<div><code>{letter}</code> ' + this.escapeHtml(trans.help_pattern_letter || 'single letter A-Z') + '</div>';
                    break;

                case 'by_id_range':
                case 'by_price_range':
                case 'by_weight_range':
                case 'by_quantity_range':
                case 'by_position_range':
                    html = '<strong>' + this.escapeHtml(trans.help_range_title || 'Numeric Range') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_range_desc || 'Filter by numeric values within specified ranges.') + '</p>';
                    html += '<p>' + this.escapeHtml(trans.help_range_tip || 'Leave min or max empty for open-ended ranges.') + '</p>';
                    break;

                case 'by_date_added':
                case 'by_date_updated':
                    html = '<strong>' + this.escapeHtml(trans.help_date_title || 'Date Range') + '</strong>';
                    html += '<p>' + this.escapeHtml(trans.help_date_desc || 'Filter by date within a specific period.') + '</p>';
                    break;

                default:
                    break;
            }

            return html;
        },

        /**
         * Build sort options HTML for a specific block type
         */
        buildSortOptions: function(blockType) {
            var options = [];

            switch (blockType) {
                case 'products':
                    options = [
                        { value: 'sales', label: 'Best sellers' },
                        { value: 'date_add', label: 'Newest' },
                        { value: 'price', label: 'Price' },
                        { value: 'name', label: 'Name' },
                        { value: 'position', label: 'Position' },
                        { value: 'quantity', label: 'Stock quantity' },
                        { value: 'random', label: 'Random' }
                    ];
                    break;

                case 'categories':
                    options = [
                        { value: 'name', label: 'Name' },
                        { value: 'position', label: 'Position' },
                        { value: 'product_count', label: 'Product count' },
                        { value: 'total_sales', label: 'Best sellers' },
                        { value: 'newest_products', label: 'Newest products' },
                        { value: 'date_add', label: 'Creation date' },
                        { value: 'random', label: 'Random' }
                    ];
                    break;

                case 'manufacturers':
                case 'suppliers':
                    options = [
                        { value: 'name', label: 'Name' },
                        { value: 'product_count', label: 'Product count' },
                        { value: 'total_sales', label: 'Best sellers' },
                        { value: 'newest_products', label: 'Newest products' },
                        { value: 'random', label: 'Random' }
                    ];
                    break;

                case 'cms':
                case 'cms_categories':
                    options = [
                        { value: 'name', label: 'Name' },
                        { value: 'position', label: 'Position' },
                        { value: 'random', label: 'Random' }
                    ];
                    break;

                default:
                    options = [
                        { value: 'name', label: 'Name' },
                        { value: 'random', label: 'Random' }
                    ];
            }

            var html = '';
            for (var i = 0; i < options.length; i++) {
                html += '<option value="' + this.escapeAttr(options[i].value) + '">' +
                        this.escapeHtml(options[i].label) + '</option>';
            }

            return html;
        },

        /**
         * Update the modifier toggle button state
         */
        updateModifierButtonState: function($group) {
            var limit = $group.find('.group-modifier-limit').val();
            var sortBy = $group.find('.group-modifier-sort').val();
            var $modifiers = $group.find('.group-modifiers');
            var $btn = $group.find('.btn-toggle-modifiers');
            var trans = this.config.trans || {};

            $btn.find('.modifier-summary').remove();

            if (limit || sortBy) {
                $modifiers.addClass('has-values');

                var summary = [];
                if (limit) {
                    summary.push((trans.top || 'Top') + ' ' + limit);
                }
                if (sortBy) {
                    var sortLabel = $group.find('.group-modifier-sort option:selected').text();
                    summary.push(sortLabel);
                }

                var $arrow = $btn.find('.toggle-arrow');
                $('<span class="modifier-summary">' + this.escapeHtml(summary.join(', ')) + '</span>').insertBefore($arrow);
            } else {
                $modifiers.removeClass('has-values');
            }
        },

        /**
         * Lock/unlock method selector when excludes are present
         */
        updateMethodSelectorLock: function($group, locked) {
            var $select = $group.find('.include-method-select');
            var $wrapper = $select.closest('.method-selector-wrapper');
            var trans = this.config.trans || {};

            if (locked) {
                $select.prop('disabled', true);

                if (!$wrapper.length) {
                    $select.wrap('<div class="method-selector-wrapper"></div>');
                    $wrapper = $select.parent('.method-selector-wrapper');
                }

                $wrapper.addClass('selector-locked');
                if (!$wrapper.find('.lock-indicator').length) {
                    var lockHtml = '<span class="mpr-info-wrapper lock-indicator">' +
                        self.esIcon('lock') +
                        '<span class="mpr-tooltip">' +
                        (trans.remove_excludes_first || 'Remove all exceptions to change selection type') +
                        '</span>' +
                        '</span>';
                    var $countEl = $wrapper.find('.condition-match-count');
                    if ($countEl.length) {
                        $countEl.before(lockHtml);
                    } else {
                        $wrapper.append(lockHtml);
                    }
                }
            } else {
                $select.prop('disabled', false);
                if ($wrapper.length) {
                    $wrapper.removeClass('selector-locked');
                    $wrapper.find('.mpr-info-wrapper.lock-indicator').remove();
                } else {
                    $select.siblings('.mpr-info-wrapper.lock-indicator').remove();
                }
            }
        }
    };

})(jQuery);

/**
 * Entity Selector - Preview Module
 * Reusable preview popover component with filter and load more
 * @partial _preview.js
 */

(function($) {
    'use strict';

    window._EntitySelectorMixins = window._EntitySelectorMixins || {};

    window._EntitySelectorMixins.preview = {

        // =========================================================================
        // HEADER & TOGGLE UPDATES
        // =========================================================================

        updateHeaderTotalCount: function() {
            var self = this;
            var total = 0;

            this.$wrapper.find('.target-block-tab .tab-badge').each(function() {
                var $badge = $(this);
                if (!$badge.hasClass('loading')) {
                    var count = parseInt($badge.text(), 10);
                    if (!isNaN(count)) {
                        total += count;
                    }
                }
            });

            var $totalBadge = this.$wrapper.find('.trait-total-count');
            if (total > 0) {
                // Update count while preserving icon structure
                var $countValue = $totalBadge.find('.count-value');
                if ($countValue.length) {
                    $countValue.text(total);
                } else {
                    // Fallback: set HTML with icon
                    $totalBadge.html(self.esIcon('visibility') + ' <span class="count-value">' + total + '</span>');
                }
                $totalBadge.show();
            } else {
                $totalBadge.hide();
            }

            this.updateShowAllToggle();
        },

        updateShowAllToggle: function() {
            var $toggle = this.$wrapper.find('.trait-show-all-toggle');
            if (!$toggle.length) return;

            var $checkbox = $toggle.find('.show-all-checkbox');
            var hasData = this.$wrapper.find('.target-block-tab.has-data').length > 0;

            $checkbox.prop('checked', !hasData);
        },

        // =========================================================================
        // REUSABLE PREVIEW POPOVER COMPONENT
        // =========================================================================

        /**
         * Create and show a reusable preview popover
         * @param {Object} options Configuration options
         * @param {jQuery} options.$badge - The badge element to position against
         * @param {Array} options.items - Array of items to display
         * @param {number} options.totalCount - Total count of items
         * @param {boolean} options.hasMore - Whether more items are available
         * @param {string} options.entityLabel - Label for items (e.g., "products")
         * @param {string} options.previewType - Type identifier (e.g., "condition", "filter-group")
         * @param {Function} options.onLoadMore - Callback when load more is clicked
         * @param {Function} options.onFilter - Callback for AJAX filtering (receives query string)
         * @param {Object} options.context - Context data for load more
         */
        createPreviewPopover: function(options) {
            var self = this;
            var trans = this.config.trans || {};

            var $badge = options.$badge;
            var items = options.items || [];
            var totalCount = options.totalCount || 0;
            var hasMore = options.hasMore || false;
            var entityLabel = options.entityLabel || 'products';
            var previewType = options.previewType || 'default';

            // Build popover HTML
            var html = '<div class="target-preview-popover preview-type-' + previewType + '">';

            // Header with count and close button
            html += '<div class="preview-header">';
            html += '<span class="preview-count">' + totalCount + ' ' + entityLabel + '</span>';
            html += '<button type="button" class="preview-close">' + this.esIcon('close') + '</button>';
            html += '</div>';

            // Filter input
            html += '<div class="preview-filter">';
            html += '<input type="text" class="preview-filter-input" placeholder="' + (trans.filter_results || 'Filter results...') + '">';
            html += '</div>';

            // Items list
            if (items.length > 0) {
                html += '<div class="preview-list">';
                html += this.renderPreviewItems(items);
                html += '</div>';

                // Load more footer with select dropdown
                if (hasMore) {
                    var remaining = totalCount - items.length;
                    html += '<div class="preview-footer">';
                    html += '<div class="load-more-controls">';
                    html += '<span class="load-more-label">' + (trans.load || 'Load') + '</span>';
                    html += '<select class="load-more-select">';
                    if (remaining >= 10) html += '<option value="10">10</option>';
                    if (remaining >= 20) html += '<option value="20" selected>20</option>';
                    if (remaining >= 50) html += '<option value="50">50</option>';
                    if (remaining >= 100) html += '<option value="100">100</option>';
                    html += '<option value="' + remaining + '">' + (trans.all || 'All') + ' (' + remaining + ')</option>';
                    html += '</select>';
                    html += '<span class="load-more-of">' + (trans.of || 'of') + ' <span class="remaining-count">' + remaining + '</span> ' + (trans.remaining || 'remaining') + '</span>';
                    html += '<button type="button" class="btn-load-more">' + self.esIcon('add') + '</button>';
                    html += '</div>';
                    html += '</div>';
                }
            } else {
                html += '<div class="preview-empty">' + (trans.no_preview || 'No items to preview') + '</div>';
            }

            html += '</div>';

            // Create and append popover
            var $popover = $(html);
            $('body').append($popover);

            // Store references
            this.$previewPopover = $popover;
            this.$previewList = $popover.find('.preview-list');
            this.previewLoadedCount = items.length;
            this.previewTotalCount = totalCount;
            this.previewContext = options.context || {};
            this.previewOnLoadMore = options.onLoadMore || null;
            this.previewOnFilter = options.onFilter || null;
            this.previewCurrentFilter = '';
            this.previewEntityLabel = entityLabel;

            // Event handlers
            $popover.find('.preview-close').on('click', function() {
                self.hidePreviewPopover();
            });

            // Filter input with AJAX support
            var $filterInput = $popover.find('.preview-filter-input');
            if (options.onFilter) {
                // Use AJAX filtering with debounce
                var debouncedFilter = this.debounce(function(query) {
                    self.previewCurrentFilter = query;
                    self.showFilterLoading(true);
                    options.onFilter.call(self, query);
                }, 300);

                $filterInput.on('input', function() {
                    var query = $(this).val().trim();
                    if (query === self.previewCurrentFilter) return;
                    debouncedFilter(query);
                });
            } else {
                // Fallback to client-side filtering
                $filterInput.on('input', function() {
                    var query = $(this).val().toLowerCase().trim();
                    self.filterPreviewItems(query);
                });
            }

            if (options.onLoadMore) {
                $popover.find('.btn-load-more').on('click', function() {
                    var $btn = $(this);
                    var $controls = $btn.closest('.load-more-controls');
                    var $select = $controls.find('.load-more-select');

                    if ($btn.hasClass('loading')) return;

                    $btn.addClass('loading');
                    $btn.find('i').replaceWith(self.esIcon('progress_activity', 'es-spin'));
                    $select.prop('disabled', true);

                    // Get selected load count
                    var loadCount = parseInt($select.val(), 10) || 20;
                    self.previewLoadCount = loadCount;

                    options.onLoadMore.call(self, $btn);
                });
            }

            // Position popover below badge
            var badgeOffset = $badge.offset();
            var badgeHeight = $badge.outerHeight();
            var badgeWidth = $badge.outerWidth();
            var popoverWidth = $popover.outerWidth();

            var leftPos = badgeOffset.left + (badgeWidth / 2) - (popoverWidth / 2);
            var minLeft = 10;
            var maxLeft = $(window).width() - popoverWidth - 10;
            leftPos = Math.max(minLeft, Math.min(leftPos, maxLeft));

            $popover.css({
                position: 'absolute',
                top: badgeOffset.top + badgeHeight + 8,
                left: leftPos,
                zIndex: 10000
            });

            // Show with transition
            $popover.addClass('show');

            return $popover;
        },

        /**
         * Update popover after loading more items
         */
        updatePreviewPopover: function(items, hasMore) {
            var trans = this.config.trans || {};

            // Update list
            this.$previewList.html(this.renderPreviewItems(items));
            this.previewLoadedCount = items.length;

            // Update or remove load more controls
            var $footer = this.$previewPopover.find('.preview-footer');
            if (hasMore) {
                var remaining = this.previewTotalCount - items.length;
                var $controls = $footer.find('.load-more-controls');
                var $btn = $controls.find('.btn-load-more');
                var $select = $controls.find('.load-more-select');

                // Reset button state
                $btn.removeClass('loading');
                $btn.find('i').replaceWith(this.esIcon('add'));
                $select.prop('disabled', false);

                // Update remaining count
                $controls.find('.remaining-count').text(remaining);

                // Update select options
                $select.empty();
                if (remaining >= 10) $select.append('<option value="10">10</option>');
                if (remaining >= 20) $select.append('<option value="20" selected>20</option>');
                if (remaining >= 50) $select.append('<option value="50">50</option>');
                if (remaining >= 100) $select.append('<option value="100">100</option>');
                $select.append('<option value="' + remaining + '">' + (trans.all || 'All') + ' (' + remaining + ')</option>');
            } else {
                $footer.remove();
            }

            // Re-apply filter if active
            var filterQuery = this.$previewPopover.find('.preview-filter-input').val();
            if (filterQuery) {
                this.filterPreviewItems(filterQuery.toLowerCase().trim());
            }
        },

        /**
         * Render preview items HTML with consistent format
         */
        renderPreviewItems: function(items) {
            var self = this;
            var html = '';

            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                var itemClass = 'preview-item';
                if (item.isCombination) itemClass += ' is-combination';

                // Build data attributes for filtering
                var dataAttrs = '';
                dataAttrs += ' data-name="' + this.escapeAttr((item.name || '').toLowerCase()) + '"';
                dataAttrs += ' data-ref="' + this.escapeAttr((item.reference || '').toLowerCase()) + '"';
                if (item.attributes) {
                    dataAttrs += ' data-attrs="' + this.escapeAttr((item.attributes || '').toLowerCase()) + '"';
                }

                html += '<div class="' + itemClass + '"' + dataAttrs + '>';

                // Image or placeholder
                if (item.image) {
                    html += '<img src="' + this.escapeAttr(item.image) + '" class="preview-item-image" alt="">';
                } else {
                    html += '<div class="preview-item-icon">' + self.esIcon('inventory_2') + '</div>';
                }

                // Info section
                html += '<div class="preview-item-info">';
                html += '<div class="preview-item-name">' + this.escapeHtml(item.name || 'Unnamed') + '</div>';

                // Meta line (reference, manufacturer, category, attributes)
                var meta = [];
                if (item.reference) {
                    meta.push('Ref: ' + item.reference);
                }
                if (item.manufacturer) {
                    meta.push(item.manufacturer);
                }
                if (item.category) {
                    meta.push(item.category);
                }
                if (item.attributes) {
                    meta.push(item.attributes);
                }

                if (meta.length > 0) {
                    html += '<div class="preview-item-meta">' + this.escapeHtml(meta.join(' • ')) + '</div>';
                }

                html += '</div>'; // .preview-item-info

                // Price column (always show if available)
                if (typeof item.price !== 'undefined' && item.price !== null) {
                    html += '<div class="preview-item-price">' + this.formatPrice(item.price) + '</div>';
                } else if (item.price_formatted) {
                    html += '<div class="preview-item-price">' + this.escapeHtml(item.price_formatted) + '</div>';
                }

                // Status badge if inactive
                if (typeof item.active !== 'undefined' && !item.active) {
                    html += '<span class="preview-item-badge badge-inactive">Inactive</span>';
                }

                html += '</div>'; // .preview-item
            }

            return html;
        },

        /**
         * Filter preview items by query (client-side fallback)
         */
        filterPreviewItems: function(query) {
            if (!this.$previewList) return;

            var $items = this.$previewList.find('.preview-item');

            if (!query) {
                $items.show();
                return;
            }

            $items.each(function() {
                var $item = $(this);
                var name = $item.data('name') || '';
                var ref = $item.data('ref') || '';
                var attrs = $item.data('attrs') || '';

                var matches = name.indexOf(query) !== -1 ||
                              ref.indexOf(query) !== -1 ||
                              attrs.indexOf(query) !== -1;

                $item.toggle(matches);
            });
        },

        /**
         * Show/hide loading indicator during AJAX filter
         */
        showFilterLoading: function(show) {
            if (!this.$previewPopover) return;

            var $list = this.$previewList;
            if (!$list) return;

            if (show) {
                // Lock the popover width before filtering to prevent resize
                if (!this.previewLockedWidth) {
                    this.previewLockedWidth = this.$previewPopover.outerWidth();
                    this.$previewPopover.css('width', this.previewLockedWidth + 'px');
                }

                $list.addClass('filtering');
                // Add overlay if not exists
                if (!$list.find('.filter-loading-overlay').length) {
                    $list.append('<div class="filter-loading-overlay">' + self.esIcon('progress_activity', 'es-spin') + '</div>');
                }
            } else {
                $list.removeClass('filtering');
                $list.find('.filter-loading-overlay').remove();
            }
        },

        /**
         * Update preview popover with filtered AJAX results
         * @param {Object} response - AJAX response with items, count, hasMore
         */
        updatePreviewPopoverFiltered: function(response) {
            var trans = this.config.trans || {};

            this.showFilterLoading(false);

            if (!response.success) {
                return;
            }

            var items = response.items || [];
            var filteredCount = response.count || 0;
            var hasMore = response.hasMore || false;

            // Update header count to show filtered count
            var $header = this.$previewPopover.find('.preview-header');
            var entityLabel = this.previewEntityLabel || 'items';
            $header.find('.preview-count').text(filteredCount + ' ' + entityLabel);

            // Update list
            if (items.length > 0) {
                this.$previewList.html(this.renderPreviewItems(items));
                this.previewLoadedCount = items.length;
                this.previewTotalCount = filteredCount;
            } else {
                var noResultsText = trans.no_filter_results || 'No matching items found';
                this.$previewList.html('<div class="preview-empty">' + noResultsText + '</div>');
                this.previewLoadedCount = 0;
                this.previewTotalCount = 0;
            }

            // Update or create footer for load more
            var $footer = this.$previewPopover.find('.preview-footer');
            if (hasMore && items.length > 0) {
                var remaining = filteredCount - items.length;
                if ($footer.length) {
                    var $controls = $footer.find('.load-more-controls');
                    var $btn = $controls.find('.btn-load-more');
                    var $select = $controls.find('.load-more-select');

                    $btn.removeClass('loading');
                    $btn.find('i').replaceWith(self.esIcon('add'));
                    $select.prop('disabled', false);
                    $controls.find('.remaining-count').text(remaining);

                    $select.empty();
                    if (remaining >= 10) $select.append('<option value="10">10</option>');
                    if (remaining >= 20) $select.append('<option value="20" selected>20</option>');
                    if (remaining >= 50) $select.append('<option value="50">50</option>');
                    if (remaining >= 100) $select.append('<option value="100">100</option>');
                    $select.append('<option value="' + remaining + '">' + (trans.all || 'All') + ' (' + remaining + ')</option>');
                } else {
                    // Create footer
                    var footerHtml = '<div class="preview-footer">';
                    footerHtml += '<div class="load-more-controls">';
                    footerHtml += '<span class="load-more-label">' + (trans.load || 'Load') + '</span>';
                    footerHtml += '<select class="load-more-select">';
                    if (remaining >= 10) footerHtml += '<option value="10">10</option>';
                    if (remaining >= 20) footerHtml += '<option value="20" selected>20</option>';
                    if (remaining >= 50) footerHtml += '<option value="50">50</option>';
                    if (remaining >= 100) footerHtml += '<option value="100">100</option>';
                    footerHtml += '<option value="' + remaining + '">' + (trans.all || 'All') + ' (' + remaining + ')</option>';
                    footerHtml += '</select>';
                    footerHtml += '<span class="load-more-of">' + (trans.of || 'of') + ' <span class="remaining-count">' + remaining + '</span> ' + (trans.remaining || 'remaining') + '</span>';
                    footerHtml += '<button type="button" class="btn-load-more">' + self.esIcon('add') + '</button>';
                    footerHtml += '</div>';
                    footerHtml += '</div>';

                    var $newFooter = $(footerHtml);
                    this.$previewList.after($newFooter);

                    // Rebind load more click
                    var self = this;
                    if (this.previewOnLoadMore) {
                        $newFooter.find('.btn-load-more').on('click', function() {
                            var $btn = $(this);
                            var $controls = $btn.closest('.load-more-controls');
                            var $select = $controls.find('.load-more-select');

                            if ($btn.hasClass('loading')) return;

                            $btn.addClass('loading');
                            $btn.find('i').replaceWith(self.esIcon('progress_activity', 'es-spin'));
                            $select.prop('disabled', true);

                            var loadCount = parseInt($select.val(), 10) || 20;
                            self.previewLoadCount = loadCount;

                            self.previewOnLoadMore.call(self, $btn);
                        });
                    }
                }
            } else {
                $footer.remove();
            }
        },

        /**
         * Format price for display
         */
        formatPrice: function(price) {
            if (typeof price !== 'number') {
                price = parseFloat(price) || 0;
            }
            // Use currency format from config if available
            var currencySign = (this.config && this.config.currency_sign) || '€';
            var currencyFormat = (this.config && this.config.currency_format) || 'right';

            var formatted = price.toFixed(2);

            if (currencyFormat === 'left') {
                return currencySign + ' ' + formatted;
            } else {
                return formatted + ' ' + currencySign;
            }
        },

        /**
         * Hide and clean up preview popover
         */
        hidePreviewPopover: function() {
            if (this.$activeBadge) {
                this.$activeBadge.removeClass('popover-open loading');
                this.$activeBadge = null;
            }
            if (this.$previewPopover) {
                this.$previewPopover.remove();
                this.$previewPopover = null;
            }
            this.$previewList = null;
            this.previewContext = null;
            this.previewOnLoadMore = null;
            this.previewOnFilter = null;
            this.previewCurrentFilter = '';
            this.previewEntityLabel = null;
            this.previewLockedWidth = null;
        },

        // =========================================================================
        // TAB PREVIEW (Block tab badge click)
        // =========================================================================

        showPreviewPopover: function($tab) {
            var self = this;
            var previewData = $tab.data('previewData');

            if (!previewData) {
                return;
            }

            this.hidePreviewPopover();

            var $badge = $tab.find('.tab-badge');
            $badge.addClass('popover-open');
            this.$activeBadge = $badge;

            var items = previewData.items || previewData.products || [];
            var blockType = $tab.data('blockType');
            var blockConfig = this.config.blocks && this.config.blocks[blockType] ? this.config.blocks[blockType] : {};
            var entityLabelPlural = blockConfig.entity_label_plural || 'items';

            this.previewBlockType = blockType;

            // If items not loaded yet, fetch them first
            if (items.length === 0 && previewData.count > 0) {
                $badge.addClass('loading');
                this.fetchTabPreviewItems($tab, function(fetchedItems, hasMore) {
                    $badge.removeClass('loading');
                    self.createPreviewPopover({
                        $badge: $badge,
                        items: fetchedItems,
                        totalCount: previewData.count,
                        hasMore: hasMore,
                        entityLabel: entityLabelPlural,
                        previewType: 'tab',
                        context: { $tab: $tab, blockType: blockType },
                        onLoadMore: function($btn) {
                            self.loadMoreTabPreviewItems($tab, $btn);
                        },
                        onFilter: function(query) {
                            self.filterTabPreviewItems($tab, query);
                        }
                    });
                });
                return;
            }

            this.createPreviewPopover({
                $badge: $badge,
                items: items,
                totalCount: previewData.count,
                hasMore: previewData.hasMore,
                entityLabel: entityLabelPlural,
                previewType: 'tab',
                context: { $tab: $tab, blockType: blockType },
                onLoadMore: function($btn) {
                    self.loadMoreTabPreviewItems($tab, $btn);
                },
                onFilter: function(query) {
                    self.filterTabPreviewItems($tab, query);
                }
            });
        },

        /**
         * Fetch preview items for a tab via AJAX
         */
        fetchTabPreviewItems: function($tab, callback) {
            var self = this;
            var blockType = $tab.data('blockType');

            var $hiddenInput = this.$wrapper.find('input[name="' + this.config.name + '"]');
            var savedData = {};
            try {
                savedData = JSON.parse($hiddenInput.val() || '{}');
            } catch (e) {
                callback([], false);
                return;
            }

            var groups = (savedData[blockType] && savedData[blockType].groups) ? savedData[blockType].groups : [];
            if (groups.length === 0) {
                callback([], false);
                return;
            }

            var data = {};
            data[blockType] = { groups: groups };

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'previewEntitySelector',
                    trait: 'EntitySelector',
                    conditions: JSON.stringify(data),
                    block_type: blockType,
                    limit: 20,
                    offset: 0
                },
                success: function(response) {
                    var items = response.items || response.products || [];
                    var hasMore = response.hasMore || (response.count > items.length);
                    // Update stored preview data with items
                    $tab.data('previewData', response);
                    callback(items, hasMore);
                },
                error: function() {
                    callback([], false);
                }
            });
        },

        /**
         * AJAX filter handler for tab preview
         */
        filterTabPreviewItems: function($tab, query) {
            var self = this;
            var blockType = this.previewBlockType;

            var $hiddenInput = this.$wrapper.find('input[name="' + this.config.name + '"]');
            var savedData = {};
            try {
                savedData = JSON.parse($hiddenInput.val() || '{}');
            } catch (e) {
                self.showFilterLoading(false);
                return;
            }

            var groups = (savedData[blockType] && savedData[blockType].groups) ? savedData[blockType].groups : [];
            if (groups.length === 0) {
                self.showFilterLoading(false);
                return;
            }

            var data = {};
            data[blockType] = { groups: groups };

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'previewTargetConditions',
                    trait: 'TargetConditions',
                    conditions: JSON.stringify(data),
                    block_type: blockType,
                    filter: query,
                    limit: 20,
                    offset: 0
                },
                success: function(response) {
                    self.updatePreviewPopoverFiltered(response);
                },
                error: function() {
                    self.showFilterLoading(false);
                }
            });
        },

        loadMoreTabPreviewItems: function($tab, $btn) {
            var self = this;
            var blockType = this.previewBlockType;

            var $hiddenInput = this.$wrapper.find('input[name="' + this.config.name + '"]');
            var savedData = {};
            try {
                savedData = JSON.parse($hiddenInput.val() || '{}');
            } catch (e) {
                return;
            }

            var groups = (savedData[blockType] && savedData[blockType].groups) ? savedData[blockType].groups : [];
            if (groups.length === 0) return;

            var data = {};
            data[blockType] = { groups: groups };

            var loadCount = this.previewLoadCount || 20;

            // Include current filter in load more request
            var ajaxData = {
                ajax: 1,
                action: 'previewTargetConditions',
                trait: 'TargetConditions',
                conditions: JSON.stringify(data),
                block_type: blockType,
                limit: self.previewLoadedCount + loadCount,
                offset: 0
            };
            if (self.previewCurrentFilter) {
                ajaxData.filter = self.previewCurrentFilter;
            }

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: ajaxData,
                success: function(response) {
                    var items = response.items || response.products || [];
                    if (response.success && items.length > 0) {
                        $tab.data('previewData', response);
                        self.previewTotalCount = response.count;
                        self.updatePreviewPopover(items, response.hasMore);
                    }
                },
                error: function() {
                    var $controls = $btn.closest('.load-more-controls');
                    var $select = $controls.find('.load-more-select');
                    $btn.removeClass('loading');
                    $btn.find('i').replaceWith(self.esIcon('add'));
                    $select.prop('disabled', false);
                }
            });
        },

        // =========================================================================
        // CONDITION PREVIEW (Single condition badge click)
        // =========================================================================

        showConditionPreviewPopover: function($badge) {
            var self = this;
            var conditionData = $badge.data('conditionData');

            if (!conditionData) {
                return;
            }

            // Check if this is a country holidays badge
            if (conditionData.isCountryHolidays && conditionData.countryIds) {
                this.showCountriesHolidayPreview($badge, conditionData.countryIds);
                return;
            }

            this.hidePreviewPopover();

            $badge.addClass('popover-open loading');
            this.$activeBadge = $badge;

            var blockType = conditionData.blockType || 'products';
            var blockConfig = this.config.blocks && this.config.blocks[blockType] ? this.config.blocks[blockType] : {};
            var entityLabelPlural = blockConfig.entity_label_plural || 'products';

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'previewConditionItems',
                    trait: 'EntitySelector',
                    method: conditionData.method,
                    values: JSON.stringify(conditionData.values),
                    block_type: blockType,
                    limit: 10
                },
                success: function(response) {
                    $badge.removeClass('loading');

                    if (response.success) {
                        self.createPreviewPopover({
                            $badge: $badge,
                            items: response.items || [],
                            totalCount: response.count,
                            hasMore: response.hasMore,
                            entityLabel: entityLabelPlural,
                            previewType: 'condition',
                            context: { conditionData: conditionData, blockType: blockType },
                            onLoadMore: function($btn) {
                                self.loadMoreConditionItems($btn);
                            },
                            onFilter: function(query) {
                                self.filterConditionItems(query);
                            }
                        });
                    } else {
                        $badge.removeClass('popover-open');
                        self.$activeBadge = null;
                    }
                },
                error: function() {
                    $badge.removeClass('loading popover-open');
                    self.$activeBadge = null;
                }
            });
        },

        /**
         * AJAX filter handler for condition preview
         */
        filterConditionItems: function(query) {
            var self = this;
            var ctx = this.previewContext;

            if (!ctx || !ctx.conditionData) {
                self.showFilterLoading(false);
                return;
            }

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'previewConditionItems',
                    trait: 'EntitySelector',
                    method: ctx.conditionData.method,
                    values: JSON.stringify(ctx.conditionData.values),
                    block_type: ctx.blockType,
                    filter: query,
                    limit: 20
                },
                success: function(response) {
                    self.updatePreviewPopoverFiltered(response);
                },
                error: function() {
                    self.showFilterLoading(false);
                }
            });
        },

        loadMoreConditionItems: function($btn) {
            var self = this;
            var ctx = this.previewContext;

            if (!ctx || !ctx.conditionData) return;

            var loadCount = this.previewLoadCount || 20;

            // Include current filter in load more request
            var ajaxData = {
                ajax: 1,
                action: 'previewConditionItems',
                trait: 'EntitySelector',
                method: ctx.conditionData.method,
                values: JSON.stringify(ctx.conditionData.values),
                block_type: ctx.blockType,
                limit: self.previewLoadedCount + loadCount
            };
            if (self.previewCurrentFilter) {
                ajaxData.filter = self.previewCurrentFilter;
            }

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: ajaxData,
                success: function(response) {
                    if (response.success) {
                        self.previewTotalCount = response.count;
                        self.updatePreviewPopover(response.items || [], response.hasMore);
                    }
                },
                error: function() {
                    var $controls = $btn.closest('.load-more-controls');
                    var $select = $controls.find('.load-more-select');
                    $btn.removeClass('loading');
                    $btn.find('i').replaceWith(self.esIcon('add'));
                    $select.prop('disabled', false);
                }
            });
        },

        // =========================================================================
        // GROUP PREVIEW (Selection group badge click)
        // =========================================================================

        showGroupPreviewPopover: function($badge, $group, blockType) {
            var self = this;

            if (!$group) {
                $group = $badge.closest('.selection-group');
            }

            if (!blockType) {
                var $block = $badge.closest('.target-block');
                blockType = $block.data('blockType') || 'products';
            }

            var groupData = $badge.data('groupData');

            if (!groupData) {
                groupData = this.serializeGroup($group, blockType);
            }

            if (!groupData || !groupData.include) {
                return;
            }

            this.hidePreviewPopover();

            $badge.addClass('popover-open loading');
            this.$activeBadge = $badge;

            var blockConfig = this.config.blocks && this.config.blocks[blockType] ? this.config.blocks[blockType] : {};
            var entityLabelPlural = blockConfig.entity_label_plural || 'products';

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'previewGroupItems',
                    trait: 'EntitySelector',
                    group_data: JSON.stringify(groupData),
                    block_type: blockType,
                    limit: 10
                },
                success: function(response) {
                    $badge.removeClass('loading');

                    if (response.success) {
                        self.createPreviewPopover({
                            $badge: $badge,
                            items: response.items || [],
                            totalCount: response.count,
                            hasMore: response.hasMore,
                            entityLabel: entityLabelPlural,
                            previewType: 'group',
                            context: { groupData: groupData, blockType: blockType, $group: $group },
                            onLoadMore: function($btn) {
                                self.loadMoreGroupItems($btn);
                            },
                            onFilter: function(query) {
                                self.filterGroupItems(query);
                            }
                        });
                    } else {
                        $badge.removeClass('popover-open');
                        self.$activeBadge = null;
                    }
                },
                error: function() {
                    $badge.removeClass('loading popover-open');
                    self.$activeBadge = null;
                }
            });
        },

        /**
         * AJAX filter handler for group preview
         */
        filterGroupItems: function(query) {
            var self = this;
            var ctx = this.previewContext;

            if (!ctx || !ctx.groupData) {
                self.showFilterLoading(false);
                return;
            }

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'previewGroupItems',
                    trait: 'EntitySelector',
                    group_data: JSON.stringify(ctx.groupData),
                    block_type: ctx.blockType,
                    filter: query,
                    limit: 20
                },
                success: function(response) {
                    self.updatePreviewPopoverFiltered(response);
                },
                error: function() {
                    self.showFilterLoading(false);
                }
            });
        },

        loadMoreGroupItems: function($btn) {
            var self = this;
            var ctx = this.previewContext;

            if (!ctx || !ctx.groupData) return;

            var loadCount = this.previewLoadCount || 20;

            // Include current filter in load more request
            var ajaxData = {
                ajax: 1,
                action: 'previewGroupItems',
                trait: 'EntitySelector',
                group_data: JSON.stringify(ctx.groupData),
                block_type: ctx.blockType,
                limit: self.previewLoadedCount + loadCount
            };
            if (self.previewCurrentFilter) {
                ajaxData.filter = self.previewCurrentFilter;
            }

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: ajaxData,
                success: function(response) {
                    if (response.success) {
                        self.previewTotalCount = response.count;
                        self.updatePreviewPopover(response.items || [], response.hasMore);
                    }
                },
                error: function() {
                    $btn.removeClass('loading');
                    $btn.find('.load-more-text').show();
                    $btn.find('.load-more-loading').hide();
                }
            });
        },

        // =========================================================================
        // FILTER GROUP PREVIEW (Attribute/Feature group toggle badge)
        // =========================================================================

        showFilterGroupPreviewPopover: function($badge, groupId, groupType, groupName) {
            var self = this;

            this.hidePreviewPopover();

            $badge.addClass('popover-open loading');
            this.$activeBadge = $badge;

            var entityLabelPlural = 'products';

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'previewFilterGroupProducts',
                    trait: 'EntitySelector',
                    group_id: groupId,
                    group_type: groupType,
                    limit: 10
                },
                success: function(response) {
                    $badge.removeClass('loading');

                    if (response.success) {
                        self.createPreviewPopover({
                            $badge: $badge,
                            items: response.items || [],
                            totalCount: response.count || 0,
                            hasMore: response.hasMore || false,
                            entityLabel: entityLabelPlural,
                            previewType: 'filter-group',
                            context: { groupId: groupId, groupType: groupType, groupName: groupName },
                            onLoadMore: function($btn) {
                                self.loadMoreFilterGroupItems($btn);
                            },
                            onFilter: function(query) {
                                self.filterFilterGroupItems(query);
                            }
                        });
                    } else {
                        $badge.removeClass('popover-open');
                        self.$activeBadge = null;
                    }
                },
                error: function() {
                    $badge.removeClass('loading popover-open');
                    self.$activeBadge = null;
                }
            });
        },

        /**
         * AJAX filter handler for filter group preview
         */
        filterFilterGroupItems: function(query) {
            var self = this;
            var ctx = this.previewContext;

            if (!ctx || !ctx.groupId) {
                self.showFilterLoading(false);
                return;
            }

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'previewFilterGroupProducts',
                    trait: 'EntitySelector',
                    group_id: ctx.groupId,
                    group_type: ctx.groupType,
                    filter: query,
                    limit: 20
                },
                success: function(response) {
                    self.updatePreviewPopoverFiltered(response);
                },
                error: function() {
                    self.showFilterLoading(false);
                }
            });
        },

        loadMoreFilterGroupItems: function($btn) {
            var self = this;
            var ctx = this.previewContext;

            if (!ctx || !ctx.groupId) return;

            var loadCount = this.previewLoadCount || 20;

            // Include current filter in load more request
            var ajaxData = {
                ajax: 1,
                action: 'previewFilterGroupProducts',
                trait: 'EntitySelector',
                group_id: ctx.groupId,
                group_type: ctx.groupType,
                limit: self.previewLoadedCount + loadCount
            };
            if (self.previewCurrentFilter) {
                ajaxData.filter = self.previewCurrentFilter;
            }

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: ajaxData,
                success: function(response) {
                    if (response.success) {
                        self.previewTotalCount = response.count;
                        self.updatePreviewPopover(response.items || [], response.hasMore);
                    }
                },
                error: function() {
                    $btn.removeClass('loading');
                    $btn.find('.load-more-text').show();
                    $btn.find('.load-more-loading').hide();
                }
            });
        },

        // =========================================================================
        // CATEGORY ITEMS PREVIEW (products/pages in a category)
        // =========================================================================

        showCategoryItemsPreview: function($badge, categoryId, categoryName, entityType) {
            var self = this;

            this.hidePreviewPopover();

            $badge.addClass('popover-open loading');
            this.$activeBadge = $badge;

            var isProducts = (entityType === 'categories');
            var entityLabelPlural = isProducts ? 'products' : 'pages';
            var action = isProducts ? 'previewCategoryProducts' : 'previewCategoryPages';

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: action,
                    trait: 'EntitySelector',
                    category_id: categoryId,
                    limit: 10
                },
                success: function(response) {
                    $badge.removeClass('loading');

                    if (response.success) {
                        self.createPreviewPopover({
                            $badge: $badge,
                            items: response.items || [],
                            totalCount: response.count || 0,
                            hasMore: response.hasMore || false,
                            entityLabel: entityLabelPlural,
                            previewType: 'category-items',
                            context: { categoryId: categoryId, categoryName: categoryName, entityType: entityType },
                            onLoadMore: function($btn) {
                                self.loadMoreCategoryItems($btn);
                            },
                            onFilter: function(query) {
                                self.filterCategoryItems(query);
                            }
                        });
                    } else {
                        $badge.removeClass('popover-open');
                        self.$activeBadge = null;
                    }
                },
                error: function() {
                    $badge.removeClass('loading popover-open');
                    self.$activeBadge = null;
                }
            });
        },

        loadMoreCategoryItems: function($btn) {
            var self = this;
            var ctx = this.previewContext;

            if (!ctx || !ctx.categoryId) return;

            var isProducts = (ctx.entityType === 'categories');
            var action = isProducts ? 'previewCategoryProducts' : 'previewCategoryPages';

            $btn.prop('disabled', true).find('i').addClass('es-spin');

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: action,
                    trait: 'EntitySelector',
                    category_id: ctx.categoryId,
                    offset: this.previewOffset,
                    limit: 10,
                    query: this.previewFilterQuery || ''
                },
                success: function(response) {
                    $btn.prop('disabled', false).find('i').removeClass('es-spin');

                    if (response.success && response.items) {
                        self.appendPreviewItems(response.items);
                        self.previewOffset += response.items.length;

                        if (!response.hasMore) {
                            $btn.hide();
                        }
                    }
                },
                error: function() {
                    $btn.prop('disabled', false).find('i').removeClass('es-spin');
                }
            });
        },

        filterCategoryItems: function(query) {
            var self = this;
            var ctx = this.previewContext;

            if (!ctx || !ctx.categoryId) {
                self.showFilterLoading(false);
                return;
            }

            var isProducts = (ctx.entityType === 'categories');
            var action = isProducts ? 'previewCategoryProducts' : 'previewCategoryPages';

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: action,
                    trait: 'EntitySelector',
                    category_id: ctx.categoryId,
                    query: query,
                    limit: 10
                },
                success: function(response) {
                    self.showFilterLoading(false);

                    if (response.success) {
                        self.replacePreviewItems(response.items || [], response.count || 0, response.hasMore || false);
                        self.previewOffset = response.items ? response.items.length : 0;
                        self.previewFilterQuery = query;
                    }
                },
                error: function() {
                    self.showFilterLoading(false);
                }
            });
        },

        // =========================================================================
        // PATTERN PREVIEW MODAL (for regex/pattern matching)
        // =========================================================================

        showPatternPreviewModal: function(pattern, entityType, caseSensitive, count) {
            var self = this;
            var trans = this.config.trans || {};

            var blockConfig = this.config.blocks && this.config.blocks[entityType] ? this.config.blocks[entityType] : {};
            var entityLabelPlural = blockConfig.entity_label_plural || 'items';
            var entityLabelSingular = blockConfig.entity_label || 'item';

            var html = '<div class="pattern-preview-modal-overlay">';
            html += '<div class="pattern-preview-modal">';
            html += '<div class="pattern-preview-header">';
            html += '<span class="pattern-preview-title">';
            html += this.esIcon('visibility') + ' ' + (trans.preview || 'Preview') + ': <code>' + this.escapeHtml(pattern) + '</code>';
            html += '</span>';
            html += '<span class="pattern-preview-count">' + count + ' ' + (count === 1 ? entityLabelSingular : entityLabelPlural) + '</span>';
            html += '<button type="button" class="pattern-preview-close">' + this.esIcon('close') + '</button>';
            html += '</div>';
            html += '<div class="pattern-preview-content">';
            html += '<div class="pattern-preview-loading">' + this.esIcon('progress_activity', 'es-spin') + ' ' + (trans.loading || 'Loading...') + '</div>';
            html += '</div>';
            html += '</div>';
            html += '</div>';

            var $modal = $(html);
            $('body').append($modal);

            $modal.find('.pattern-preview-close').on('click', function() {
                $modal.remove();
            });
            $modal.on('click', function(e) {
                if ($(e.target).hasClass('pattern-preview-modal-overlay')) {
                    $modal.remove();
                }
            });

            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'previewPatternMatches',
                    trait: 'TargetConditions',
                    pattern: pattern,
                    entity_type: entityType,
                    case_sensitive: caseSensitive ? 1 : 0,
                    limit: 50
                },
                success: function(response) {
                    if (response.success && response.items) {
                        var items = response.items;
                        var listHtml = '<div class="pattern-preview-list">';

                        if (items.length === 0) {
                            listHtml += '<div class="pattern-preview-empty">' + (trans.no_matches || 'No matches found') + '</div>';
                        } else {
                            for (var i = 0; i < items.length; i++) {
                                var item = items[i];
                                listHtml += '<div class="pattern-preview-item">';
                                if (item.image) {
                                    listHtml += '<img src="' + self.escapeAttr(item.image) + '" alt="" class="preview-item-image">';
                                }
                                listHtml += '<span class="preview-item-name">' + self.escapeHtml(item.name) + '</span>';
                                if (item.id) {
                                    listHtml += '<span class="preview-item-id">#' + item.id + '</span>';
                                }
                                listHtml += '</div>';
                            }

                            if (count > items.length) {
                                listHtml += '<div class="pattern-preview-more">... ' + (trans.and || 'and') + ' ' + (count - items.length) + ' ' + (trans.more || 'more') + '</div>';
                            }
                        }

                        listHtml += '</div>';
                        $modal.find('.pattern-preview-content').html(listHtml);
                    } else {
                        $modal.find('.pattern-preview-content').html('<div class="pattern-preview-error">' + (trans.error_loading || 'Error loading preview') + '</div>');
                    }
                },
                error: function() {
                    $modal.find('.pattern-preview-content').html('<div class="pattern-preview-error">' + (trans.error_loading || 'Error loading preview') + '</div>');
                }
            });
        },

        // =========================================================================
        // HELPER METHODS
        // =========================================================================

        refreshGroupPreviewIfOpen: function($group) {
            // Check if preview is for this group and refresh if needed
            if (!this.$activeBadge || !this.$previewPopover) {
                return;
            }
        },

        /**
         * Escape HTML special characters
         */
        escapeHtml: function(str) {
            if (!str) return '';
            return String(str)
                .replace(/&/g, '&amp;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#39;');
        },

        /**
         * Escape attribute value
         */
        escapeAttr: function(str) {
            if (!str) return '';
            return String(str)
                .replace(/&/g, '&amp;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#39;');
        },

        // =========================================================================
        // TOTAL COUNT PREVIEW (Header total badge click)
        // =========================================================================

        /**
         * Show preview popover for total count badge
         * Displays a summary of all entity types with their counts
         */
        showTotalPreviewPopover: function($badge) {
            var self = this;
            var trans = this.config.trans || {};

            this.hidePreviewPopover();

            $badge.addClass('popover-open');
            this.$activeBadge = $badge;

            // Collect all entity types with data
            var summaryItems = [];
            this.$wrapper.find('.target-block-tab.has-data').each(function() {
                var $tab = $(this);
                var blockType = $tab.data('blockType');
                var $tabBadge = $tab.find('.tab-badge');
                var countText = $tabBadge.text().replace(/[^0-9]/g, '');
                var count = parseInt(countText, 10) || 0;

                if (count > 0) {
                    var blockConfig = self.config.blocks && self.config.blocks[blockType] ? self.config.blocks[blockType] : {};
                    var icon = $tab.find('.tab-label').prev('i').text() || 'widgets';
                    var label = $tab.find('.tab-label').text() || blockType;

                    summaryItems.push({
                        blockType: blockType,
                        label: label,
                        icon: icon,
                        count: count
                    });
                }
            });

            // Build popover HTML
            var totalCount = parseInt($badge.find('.count-value').text(), 10) || 0;
            var popoverHtml = '<div class="target-preview-popover total-preview-popover">';
            popoverHtml += '<div class="preview-popover-header">';
            popoverHtml += '<span class="preview-popover-title">' + (trans.total_summary || 'Selection Summary') + '</span>';
            popoverHtml += '<span class="preview-popover-count">' + totalCount + ' ' + (trans.total_items || 'total items') + '</span>';
            popoverHtml += '</div>';
            popoverHtml += '<div class="preview-popover-body">';
            popoverHtml += '<ul class="total-summary-list">';

            for (var i = 0; i < summaryItems.length; i++) {
                var item = summaryItems[i];
                popoverHtml += '<li class="total-summary-item" data-block-type="' + item.blockType + '">';
                popoverHtml += this.esIcon(item.icon);
                popoverHtml += '<span class="summary-item-label">' + self.escapeHtml(item.label) + '</span>';
                popoverHtml += '<span class="summary-item-count">' + item.count + '</span>';
                popoverHtml += '</li>';
            }

            popoverHtml += '</ul>';
            popoverHtml += '</div>';
            popoverHtml += '</div>';

            var $popover = $(popoverHtml);
            this.$previewPopover = $popover;

            // Click on item to switch to that tab
            $popover.on('click', '.total-summary-item', function() {
                var blockType = $(this).data('blockType');
                self.hidePreviewPopover();
                self.switchToBlock(blockType);
            });

            // Position popover
            $('body').append($popover);
            var badgeOffset = $badge.offset();
            var badgeHeight = $badge.outerHeight();
            var popoverWidth = $popover.outerWidth();

            $popover.css({
                position: 'absolute',
                top: badgeOffset.top + badgeHeight + 5,
                left: badgeOffset.left - (popoverWidth / 2) + ($badge.outerWidth() / 2),
                zIndex: 10000
            });

            // Adjust if off screen
            var windowWidth = $(window).width();
            var popoverRight = $popover.offset().left + popoverWidth;
            if (popoverRight > windowWidth - 10) {
                $popover.css('left', windowWidth - popoverWidth - 10);
            }
            if ($popover.offset().left < 10) {
                $popover.css('left', 10);
            }

            $popover.hide().fadeIn(150);
        },

        // =========================================================================
        // HOLIDAY PREVIEW (Country chip eye button click)
        // =========================================================================

        /**
         * Show holiday preview popover for a country
         * @param {number} countryId - Country ID
         * @param {string} countryName - Country name
         * @param {string} countryIso - Country ISO code
         * @param {jQuery} $trigger - The button element that triggered this
         */
        showHolidayPreview: function(countryId, countryName, countryIso, $trigger) {
            var self = this;
            var trans = this.config.trans || {};

            // Close any existing holiday popover
            $('.holiday-preview-popover').remove();

            // Create popover HTML
            var popoverHtml = '<div class="holiday-preview-popover target-preview-popover show">';
            popoverHtml += '<div class="popover-header">';
            popoverHtml += '<span class="popover-title">';
            if (countryIso) {
                popoverHtml += '<img src="https://flagcdn.com/20x15/' + this.escapeAttr(countryIso.toLowerCase()) + '.png" alt="" class="popover-flag"> ';
            }
            popoverHtml += this.escapeHtml(countryName) + ' - ' + (trans.holidays || 'Holidays');
            popoverHtml += '</span>';
            popoverHtml += '<button type="button" class="popover-close">' + this.esIcon('close') + '</button>';
            popoverHtml += '</div>';
            popoverHtml += '<div class="popover-body">';
            popoverHtml += '<div class="holiday-preview-loading">' + this.esIcon('sync', 'es-spin') + ' ' + (trans.loading || 'Loading...') + '</div>';
            popoverHtml += '</div>';
            popoverHtml += '</div>';

            var $popover = $(popoverHtml);
            $('body').append($popover);

            // Position popover near the trigger button
            var triggerRect = $trigger[0].getBoundingClientRect();
            var scrollTop = $(window).scrollTop();
            var scrollLeft = $(window).scrollLeft();
            var popoverWidth = $popover.outerWidth();
            var popoverHeight = $popover.outerHeight();
            var windowWidth = $(window).width();
            var windowHeight = $(window).height();

            // Default: position below and to the right of the trigger
            var top = triggerRect.bottom + scrollTop + 8;
            var left = triggerRect.left + scrollLeft;

            // Adjust horizontal position if it goes off-screen
            if (left + popoverWidth > windowWidth - 10) {
                left = windowWidth - popoverWidth - 10;
            }
            if (left < 10) {
                left = 10;
            }

            // Adjust vertical position if it goes below viewport
            if (triggerRect.bottom + popoverHeight > windowHeight - 10) {
                // Position above the trigger instead
                top = triggerRect.top + scrollTop - popoverHeight - 8;
            }

            $popover.css({
                position: 'absolute',
                top: top,
                left: left,
                zIndex: 10001
            });

            // Close button handler
            $popover.find('.popover-close').on('click', function() {
                $popover.remove();
            });

            // Fetch holidays via AJAX
            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'getHolidaysPreview',
                    trait: 'EntitySelector',
                    id_country: countryId
                },
                success: function(response) {
                    if (response.success && response.holidays && response.holidays.length > 0) {
                        var listHtml = '<div class="holiday-list">';
                        for (var i = 0; i < response.holidays.length; i++) {
                            var h = response.holidays[i];
                            var typeClass = h.type ? 'holiday-type-' + h.type.toLowerCase().replace(/\s+/g, '-') : '';
                            listHtml += '<div class="holiday-item ' + typeClass + '">';
                            listHtml += '<div class="holiday-date">';
                            listHtml += '<span class="holiday-day">' + self.escapeHtml(h.date_formatted || h.date) + '</span>';
                            if (h.day_of_week) {
                                listHtml += '<span class="holiday-weekday">' + self.escapeHtml(h.day_of_week) + '</span>';
                            }
                            listHtml += '</div>';
                            listHtml += '<div class="holiday-info">';
                            listHtml += '<span class="holiday-name">' + self.escapeHtml(h.name) + '</span>';
                            if (h.type) {
                                listHtml += '<span class="holiday-type-badge">' + self.escapeHtml(h.type) + '</span>';
                            }
                            listHtml += '</div>';
                            listHtml += '</div>';
                        }
                        listHtml += '</div>';

                        if (response.total_count) {
                            listHtml += '<div class="holiday-preview-note">' + response.total_count + ' ' + (trans.upcoming_holidays || 'upcoming holidays') + '</div>';
                        }

                        $popover.find('.popover-body').html(listHtml);
                    } else {
                        var noDataHtml = '<div class="holiday-preview-empty">';
                        noDataHtml += self.esIcon('event_busy');
                        noDataHtml += '<p>' + (trans.no_holidays || 'No holidays found') + '</p>';
                        noDataHtml += '</div>';
                        $popover.find('.popover-body').html(noDataHtml);
                    }

                    // Re-adjust position after content loaded
                    var newPopoverHeight = $popover.outerHeight();
                    if (triggerRect.bottom + newPopoverHeight > windowHeight - 10) {
                        var newTop = triggerRect.top + scrollTop - newPopoverHeight - 8;
                        if (newTop > 10) {
                            $popover.css('top', newTop);
                        }
                    }
                },
                error: function() {
                    var errorHtml = '<div class="holiday-preview-empty">';
                    errorHtml += self.esIcon('error');
                    errorHtml += '<p>' + (trans.error_loading || 'Error loading holidays') + '</p>';
                    errorHtml += '</div>';
                    $popover.find('.popover-body').html(errorHtml);
                }
            });
        },

        // =========================================================================
        // COUNTRIES HOLIDAY PREVIEW (Condition badge for multiple countries)
        // =========================================================================

        /**
         * Show holiday preview popover for multiple selected countries
         * @param {jQuery} $badge - The condition-match-count badge element
         * @param {Array} countryIds - Array of country IDs
         */
        showCountriesHolidayPreview: function($badge, countryIds) {
            var self = this;
            var trans = this.config.trans || {};

            // Close any existing popovers
            this.hidePreviewPopover();
            $('.holiday-preview-popover').remove();

            // Create popover HTML with placeholder title (will update after AJAX)
            var popoverHtml = '<div class="holiday-preview-popover target-preview-popover show">';
            popoverHtml += '<div class="popover-header">';
            popoverHtml += '<span class="popover-title">' + this.esIcon('sync', 'es-spin') + ' ' + (trans.loading || 'Loading...') + '</span>';
            popoverHtml += '<button type="button" class="popover-close">' + this.esIcon('close') + '</button>';
            popoverHtml += '</div>';
            popoverHtml += '<div class="popover-filter">';
            popoverHtml += this.esIcon('search');
            popoverHtml += '<input type="text" class="holiday-filter-input" placeholder="' + (trans.filter_holidays || 'Filter by country, date, name...') + '">';
            popoverHtml += '</div>';
            popoverHtml += '<div class="popover-body">';
            popoverHtml += '<div class="holiday-preview-loading">' + this.esIcon('sync', 'es-spin') + ' ' + (trans.loading || 'Loading...') + '</div>';
            popoverHtml += '</div>';
            popoverHtml += '</div>';

            var $popover = $(popoverHtml);
            $('body').append($popover);

            // Position popover near the badge
            var badgeRect = $badge[0].getBoundingClientRect();
            var scrollTop = $(window).scrollTop();
            var scrollLeft = $(window).scrollLeft();
            var popoverWidth = $popover.outerWidth();
            var popoverHeight = $popover.outerHeight();
            var windowWidth = $(window).width();
            var windowHeight = $(window).height();

            // Default: position below the badge
            var top = badgeRect.bottom + scrollTop + 8;
            var left = badgeRect.left + scrollLeft;

            // Adjust horizontal position
            if (left + popoverWidth > windowWidth - 10) {
                left = windowWidth - popoverWidth - 10;
            }
            if (left < 10) {
                left = 10;
            }

            // Adjust vertical position if it goes below viewport
            if (badgeRect.bottom + popoverHeight > windowHeight - 10) {
                top = badgeRect.top + scrollTop - popoverHeight - 8;
            }

            $popover.css({
                position: 'absolute',
                top: top,
                left: left,
                zIndex: 10001
            });

            // Mark badge as open
            $badge.addClass('popover-open');
            this.$activeBadge = $badge;

            // Close button handler
            $popover.find('.popover-close').on('click', function() {
                $popover.remove();
                $badge.removeClass('popover-open');
                self.$activeBadge = null;
            });

            // Fetch holidays via AJAX
            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'getHolidaysForCountries',
                    trait: 'EntitySelector',
                    country_ids: countryIds.join(','),
                    count_only: 0
                },
                success: function(response) {
                    if (response.success && response.holidays && response.holidays.length > 0) {
                        // Count holidays per country to determine which flags to show
                        var countryHolidayCounts = {};
                        var countriesMap = {};

                        // Build map of countries from response
                        if (response.countries) {
                            for (var ci = 0; ci < response.countries.length; ci++) {
                                var cInfo = response.countries[ci];
                                countriesMap[cInfo.id] = cInfo;
                                countryHolidayCounts[cInfo.id] = 0;
                            }
                        }

                        // Count holidays per country
                        for (var hi = 0; hi < response.holidays.length; hi++) {
                            var hol = response.holidays[hi];
                            if (hol.country_id && typeof countryHolidayCounts[hol.country_id] !== 'undefined') {
                                countryHolidayCounts[hol.country_id]++;
                            }
                        }

                        // Sort countries by holiday count (descending)
                        var sortedCountries = Object.keys(countriesMap).sort(function(a, b) {
                            return (countryHolidayCounts[b] || 0) - (countryHolidayCounts[a] || 0);
                        }).map(function(id) {
                            return countriesMap[id];
                        });

                        // Build header title with flags for countries with most holidays
                        var titleHtml = '';
                        var numCountries = sortedCountries.length;
                        if (numCountries <= 3) {
                            for (var fi = 0; fi < numCountries; fi++) {
                                var fc = sortedCountries[fi];
                                if (fc && fc.iso_code) {
                                    titleHtml += '<img src="https://flagcdn.com/20x15/' + self.escapeAttr(fc.iso_code.toLowerCase()) + '.png" alt="' + self.escapeAttr(fc.iso_code) + '" class="popover-flag" title="' + self.escapeAttr(fc.name) + '"> ';
                                }
                            }
                        } else {
                            // Show top 2 flags (countries with most holidays) + count
                            for (var fj = 0; fj < 2; fj++) {
                                var fcc = sortedCountries[fj];
                                if (fcc && fcc.iso_code) {
                                    titleHtml += '<img src="https://flagcdn.com/20x15/' + self.escapeAttr(fcc.iso_code.toLowerCase()) + '.png" alt="" class="popover-flag" title="' + self.escapeAttr(fcc.name) + '"> ';
                                }
                            }
                            titleHtml += '+' + (numCountries - 2) + ' ';
                        }
                        titleHtml += response.total_count + ' ' + (trans.holidays || 'Holidays');

                        // Update header title
                        $popover.find('.popover-title').html(titleHtml);

                        var listHtml = '<div class="holiday-list">';
                        for (var i = 0; i < response.holidays.length; i++) {
                            var h = response.holidays[i];
                            var typeClass = h.type ? 'holiday-type-' + h.type.toLowerCase().replace(/\s+/g, '-') : '';
                            // Build search text for filtering
                            var searchText = [
                                h.name || '',
                                h.date_formatted || h.date || '',
                                h.day_of_week || '',
                                h.country_name || '',
                                h.type || ''
                            ].join(' ').toLowerCase();
                            listHtml += '<div class="holiday-item ' + typeClass + '" data-search="' + self.escapeAttr(searchText) + '">';
                            listHtml += '<div class="holiday-date">';
                            listHtml += '<span class="holiday-day">' + self.escapeHtml(h.date_formatted || h.date) + '</span>';
                            if (h.day_of_week) {
                                listHtml += '<span class="holiday-weekday">' + self.escapeHtml(h.day_of_week) + '</span>';
                            }
                            listHtml += '</div>';
                            listHtml += '<div class="holiday-info">';
                            // Show country flag before holiday name when multiple countries
                            if (h.country_iso && countryIds.length > 1) {
                                listHtml += '<img src="https://flagcdn.com/16x12/' + self.escapeAttr(h.country_iso.toLowerCase()) + '.png" alt="" class="holiday-country-flag" title="' + self.escapeAttr(h.country_name || '') + '"> ';
                            }
                            listHtml += '<span class="holiday-name">' + self.escapeHtml(h.name) + '</span>';
                            if (h.type) {
                                listHtml += '<span class="holiday-type-badge">' + self.escapeHtml(h.type) + '</span>';
                            }
                            listHtml += '</div>';
                            listHtml += '</div>';
                        }
                        listHtml += '</div>';

                        if (response.total_count && countryIds.length > 1) {
                            var noteText = (trans.across_countries || 'across') + ' ' + countryIds.length + ' ' + (trans.countries || 'countries');
                            listHtml += '<div class="holiday-preview-note">' + noteText + '</div>';
                        }

                        $popover.find('.popover-body').html(listHtml);

                        // Setup filter input handler
                        $popover.find('.holiday-filter-input').on('input', function() {
                            var query = $(this).val().toLowerCase().trim();
                            var $items = $popover.find('.holiday-item');
                            var visibleCount = 0;

                            $items.each(function() {
                                var searchData = $(this).attr('data-search') || '';
                                if (!query || searchData.indexOf(query) !== -1) {
                                    $(this).show();
                                    visibleCount++;
                                } else {
                                    $(this).hide();
                                }
                            });

                            // Update note with filtered count
                            var $note = $popover.find('.holiday-preview-note');
                            if (query && $note.length) {
                                $note.text(visibleCount + ' ' + (trans.matches || 'matches'));
                            } else if ($note.length && countryIds.length > 1) {
                                var noteText = (trans.across_countries || 'across') + ' ' + countryIds.length + ' ' + (trans.countries || 'countries');
                                $note.text(noteText);
                            }
                        });
                    } else {
                        // Update header for empty state
                        $popover.find('.popover-title').html('0 ' + (trans.holidays || 'Holidays'));

                        var noDataHtml = '<div class="holiday-preview-empty">';
                        noDataHtml += self.esIcon('event_busy');
                        noDataHtml += '<p>' + (trans.no_holidays || 'No holidays found') + '</p>';
                        noDataHtml += '</div>';
                        $popover.find('.popover-body').html(noDataHtml);
                    }

                    // Re-adjust position after content loaded
                    var newPopoverHeight = $popover.outerHeight();
                    if (badgeRect.bottom + newPopoverHeight > windowHeight - 10) {
                        var newTop = badgeRect.top + scrollTop - newPopoverHeight - 8;
                        if (newTop > 10) {
                            $popover.css('top', newTop);
                        }
                    }
                },
                error: function() {
                    // Update header for error state
                    $popover.find('.popover-title').html(self.esIcon('error') + ' ' + (trans.error || 'Error'));

                    var errorHtml = '<div class="holiday-preview-empty">';
                    errorHtml += self.esIcon('error');
                    errorHtml += '<p>' + (trans.error_loading || 'Error loading holidays') + '</p>';
                    errorHtml += '</div>';
                    $popover.find('.popover-body').html(errorHtml);
                }
            });
        }
    };

})(jQuery);

/**
 * Entity Selector - Category Tree Module
 * Hierarchical tree view for category selection inside the dropdown
 * @partial _tree.js
 *
 * Features:
 * - Expand/collapse individual nodes
 * - Expand all / Collapse all
 * - Select parent with all children button
 * - Visual tree with indentation
 * - Product count display
 * - Search/filter within tree
 */

(function($) {
    'use strict';

    // Create mixin namespace
    window._EntitySelectorMixins = window._EntitySelectorMixins || {};

    // Tree mixin
    window._EntitySelectorMixins.tree = {

        // Tree state
        treeData: null,
        treeFlatData: null,

        /**
         * Load and display category tree in the dropdown
         * Called when view mode is changed to "tree"
         */
        loadCategoryTree: function() {
            var self = this;
            var $results = this.$dropdown.find('.dropdown-results');
            var trans = this.config.trans || {};
            var searchEntity = this.activeGroup ? this.activeGroup.searchEntity : 'categories';

            // Show loading
            $results.html('<div class="tree-loading">' + this.esIcon('progress_activity', 'es-spin') + ' ' +
                this.escapeHtml(trans.loading || 'Loading...') + '</div>');

            // Fetch tree data
            $.ajax({
                url: this.config.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    ajax: 1,
                    action: 'getCategoryTree',
                    trait: 'EntitySelector',
                    entity_type: searchEntity
                },
                success: function(response) {
                    if (response.success && response.categories && response.categories.length > 0) {
                        self.treeFlatData = response.categories;
                        self.treeData = self.buildTreeStructure(response.categories);
                        self.renderCategoryTree($results, searchEntity);
                    } else {
                        $results.html('<div class="dropdown-empty">' +
                            self.escapeHtml(trans.no_categories || 'No categories found') + '</div>');
                    }
                },
                error: function() {
                    $results.html('<div class="dropdown-error">' +
                        self.escapeHtml(trans.error_loading || 'Failed to load categories') + '</div>');
                }
            });
        },

        /**
         * Build nested tree structure from flat array
         * @param {Array} flatData - Flat array with parent_id references
         * @returns {Array} Nested tree structure
         */
        buildTreeStructure: function(flatData) {
            var lookup = {};
            var tree = [];

            // Create lookup and initialize children arrays
            flatData.forEach(function(item) {
                lookup[item.id] = $.extend({}, item, { children: [] });
            });

            // Build tree by assigning children to parents
            flatData.forEach(function(item) {
                var node = lookup[item.id];
                var parentId = parseInt(item.parent_id, 10);

                if (parentId && lookup[parentId]) {
                    lookup[parentId].children.push(node);
                } else {
                    tree.push(node);
                }
            });

            return tree;
        },

        /**
         * Render the category tree inside dropdown results
         * @param {jQuery} $container - The dropdown-results container
         * @param {string} entityType - 'categories' or 'cms_categories'
         */
        renderCategoryTree: function($container, entityType) {
            var self = this;
            var trans = this.config.trans || {};

            // Get currently selected IDs from chips
            var selectedIds = this.getSelectedIdsFromChips();

            // Build tree HTML
            var html = '<div class="category-tree" data-entity-type="' + this.escapeAttr(entityType) + '">';

            // Tree toolbar
            html += '<div class="tree-toolbar">';
            html += '<button type="button" class="btn-expand-all" title="' +
                this.escapeAttr(trans.expand_all || 'Expand all') + '">';
            html += this.esIcon('add_box') + ' ' + this.escapeHtml(trans.expand_all || 'Expand all');
            html += '</button>';
            html += '<button type="button" class="btn-collapse-all" title="' +
                this.escapeAttr(trans.collapse_all || 'Collapse all') + '">';
            html += this.esIcon('indeterminate_check_box') + ' ' + this.escapeHtml(trans.collapse_all || 'Collapse all');
            html += '</button>';
            html += '</div>';

            // Tree items
            html += '<div class="tree-items">';
            html += this.renderTreeItems(this.treeData, 0, selectedIds);
            html += '</div>';

            html += '</div>';

            $container.html(html);

            // Update count
            var totalCount = this.treeFlatData ? this.treeFlatData.length : 0;
            var selectedCount = selectedIds.length;
            var categoryLabel = entityType === 'cms_categories' ? 'CMS categories' : 'categories';
            var countText = totalCount + ' ' + categoryLabel;
            if (selectedCount > 0) {
                countText += ' (' + selectedCount + ' selected)';
            }
            this.$dropdown.find('.results-count').text(countText);

            // Update select children button states
            this.updateSelectChildrenButtons(this.$dropdown.find('.tree-item'));
        },

        /**
         * Render tree items recursively
         * @param {Array} nodes - Tree nodes
         * @param {number} level - Current depth level
         * @param {Array} selectedIds - Currently selected IDs
         * @returns {string} HTML string
         */
        renderTreeItems: function(nodes, level, selectedIds) {
            var self = this;
            var html = '';
            var trans = this.config.trans || {};

            nodes.forEach(function(node) {
                var hasChildren = node.children && node.children.length > 0;
                var isSelected = selectedIds.indexOf(parseInt(node.id, 10)) !== -1;
                var indent = level * 20;

                var itemClass = 'tree-item';
                if (hasChildren) itemClass += ' has-children';
                if (isSelected) itemClass += ' selected';
                if (!node.active) itemClass += ' inactive';

                html += '<div class="' + itemClass + '" data-id="' + node.id + '" ';
                html += 'data-name="' + self.escapeAttr(node.name) + '" ';
                html += 'data-level="' + level + '" ';
                html += 'data-parent-id="' + (node.parent_id || 0) + '">';

                // Indentation
                html += '<span class="tree-indent" style="width: ' + indent + 'px;"></span>';

                // Toggle button (expand/collapse)
                if (hasChildren) {
                    html += '<span class="tree-toggle">' + self.esIcon('arrow_drop_down') + '</span>';
                    // Select with children button (next to toggle on the left)
                    html += '<button type="button" class="btn-select-children" title="' +
                        self.escapeAttr(trans.select_with_children || 'Select with all children') + '">';
                    html += self.esIcon('check_box');
                    html += '</button>';
                } else {
                    html += '<span class="tree-toggle tree-leaf"></span>';
                }

                // Checkbox indicator
                html += '<span class="tree-checkbox">' + this.esIcon('check') + '</span>';

                // Category icon
                html += '<span class="tree-icon">' + this.esIcon('folder') + '</span>';

                // Name
                html += '<span class="tree-name">' + self.escapeHtml(node.name) + '</span>';

                // Product/page count with clickable preview
                var itemCount = node.product_count || node.page_count || 0;
                if (itemCount > 0) {
                    var countLabel = node.page_count ? (trans.pages || 'pages') : (trans.products || 'products');
                    html += '<span class="tree-count clickable" data-category-id="' + node.id + '" ';
                    html += 'title="' + self.escapeAttr(itemCount + ' ' + countLabel) + '">';
                    html += self.esIcon('visibility') + ' ' + itemCount;
                    html += '</span>';
                }

                // Inactive badge
                if (!node.active) {
                    html += '<span class="tree-badge inactive">' +
                        self.escapeHtml(trans.inactive || 'Inactive') + '</span>';
                }

                html += '</div>';

                // Render children
                if (hasChildren) {
                    html += '<div class="tree-children">';
                    html += self.renderTreeItems(node.children, level + 1, selectedIds);
                    html += '</div>';
                }
            });

            return html;
        },

        /**
         * Get selected IDs from the current picker's chips
         * @returns {Array} Array of selected IDs
         */
        getSelectedIdsFromChips: function() {
            var selectedIds = [];

            if (!this.activeGroup) return selectedIds;

            var $block = this.$wrapper.find('.target-block[data-block-type="' + this.activeGroup.blockType + '"]');
            var $group = $block.find('.selection-group[data-group-index="' + this.activeGroup.groupIndex + '"]');
            var $picker;

            if (this.activeGroup.section === 'include') {
                $picker = $group.find('.include-picker');
            } else {
                var $excludeRow = $group.find('.exclude-row[data-exclude-index="' + this.activeGroup.excludeIndex + '"]');
                $picker = $excludeRow.find('.exclude-picker');
            }

            $picker.find('.entity-chip').each(function() {
                selectedIds.push(parseInt($(this).data('id'), 10));
            });

            return selectedIds;
        },

        /**
         * Filter category tree by search query
         * @param {string} query - Search query
         */
        filterCategoryTree: function(query) {
            var $tree = this.$dropdown.find('.category-tree');
            if (!$tree.length) return;

            var $items = $tree.find('.tree-item');
            var $children = $tree.find('.tree-children');
            query = (query || '').toLowerCase().trim();

            // Remove any inline display styles set by jQuery .toggle()
            $items.css('display', '');

            if (!query) {
                $items.removeClass('filtered-out filter-match');
                $children.removeClass('filter-expanded');
                return;
            }

            // Mark all as filtered out first
            $items.addClass('filtered-out').removeClass('filter-match');

            // Find matching items and show them with their parents
            $items.each(function() {
                var $item = $(this);
                var name = ($item.data('name') || '').toLowerCase();

                if (name.indexOf(query) !== -1) {
                    $item.removeClass('filtered-out');

                    // Show parent containers
                    $item.parents('.tree-children').addClass('filter-expanded');
                    $item.parents('.tree-item').removeClass('filtered-out');

                    // Show children of matching item
                    $item.next('.tree-children').find('.tree-item').removeClass('filtered-out');
                    $item.next('.tree-children').addClass('filter-expanded');
                }
            });
        },

        /**
         * Find all descendant tree items of a given item
         * @param {jQuery} $item - Parent tree item
         * @param {jQuery} $allItems - All tree items (for performance)
         * @returns {Array} Array of descendant jQuery elements
         */
        findTreeDescendants: function($item, $allItems) {
            var descendants = [];
            var parentId = parseInt($item.data('id'), 10);
            var level = parseInt($item.data('level'), 10);

            // Find immediate children first
            var $next = $item.next('.tree-children');
            if ($next.length) {
                $next.find('.tree-item').each(function() {
                    descendants.push(this);
                });
            }

            return descendants;
        },

        /**
         * Update the state of select-children buttons based on selection
         * @param {jQuery} $allItems - All tree items
         */
        updateSelectChildrenButtons: function($allItems) {
            var self = this;
            var trans = this.config.trans || {};

            $allItems.filter('.has-children').each(function() {
                var $item = $(this);
                var $btn = $item.find('.btn-select-children');
                if (!$btn.length) return;

                var $children = $item.next('.tree-children');
                if (!$children.length) return;

                var $childItems = $children.find('.tree-item');
                var isParentSelected = $item.hasClass('selected');
                var allChildrenSelected = true;

                $childItems.each(function() {
                    if (!$(this).hasClass('selected')) {
                        allChildrenSelected = false;
                        return false;
                    }
                });

                if (isParentSelected && allChildrenSelected) {
                    $btn.find('i').replaceWith(self.esIcon('indeterminate_check_box'));
                    $btn.attr('title', trans.deselect_with_children || 'Deselect with all children');
                } else {
                    $btn.find('i').replaceWith(self.esIcon('add_box'));
                    $btn.attr('title', trans.select_with_children || 'Select with all children');
                }
            });
        }
    };

})(jQuery);

/**
 * Entity Selector - Validation Module
 * Conflict detection and prevention for entity selections
 * @partial _validation.js
 *
 * Features:
 * - Same entity in include & exclude detection
 * - Parent-child conflict detection for tree entities
 * - Redundant selection detection
 * - Error message display
 */

(function($) {
    'use strict';

    window._EntitySelectorMixins = window._EntitySelectorMixins || {};

    window._EntitySelectorMixins.validation = {

        /**
         * Validate a selection before adding it
         * Returns { valid: true } or { valid: false, error: 'message', type: 'conflict_type' }
         *
         * @param {number|string} id - Entity ID to validate
         * @param {string} name - Entity name (for error messages)
         * @param {string} section - 'include' or 'exclude'
         * @param {Object} data - Additional data (parent_id, etc.)
         * @returns {Object} Validation result
         */
        validateSelection: function(id, name, section, data) {
            if (!this.activeGroup) {
                return { valid: true };
            }

            var trans = this.config.trans || {};
            id = parseInt(id, 10);

            var $block = this.$wrapper.find('.target-block[data-block-type="' + this.activeGroup.blockType + '"]');
            var $group = $block.find('.selection-group[data-group-index="' + this.activeGroup.groupIndex + '"]');

            // Get include chips
            var includeIds = this.getChipIds($group.find('.include-picker'));

            // Get all exclude chips (from all exclude rows)
            var excludeIds = [];
            $group.find('.exclude-row').each(function() {
                var $excludePicker = $(this).find('.exclude-picker');
                var ids = [];
                $excludePicker.find('.entity-chip').each(function() {
                    ids.push(parseInt($(this).data('id'), 10));
                });
                excludeIds = excludeIds.concat(ids);
            });

            // 1. Check for same entity in include & exclude
            var conflictResult = this.checkIncludeExcludeConflict(id, name, section, includeIds, excludeIds, trans);
            if (!conflictResult.valid) {
                return conflictResult;
            }

            // 2. Check for redundant selection (already selected in same section)
            var redundantResult = this.checkRedundantSelection(id, name, section, includeIds, excludeIds, trans);
            if (!redundantResult.valid) {
                return redundantResult;
            }

            // 3. Check for parent-child conflicts (only for tree entities)
            var searchEntity = this.activeGroup.searchEntity;
            if (searchEntity === 'categories' || searchEntity === 'cms_categories') {
                var treeResult = this.checkTreeConflicts(id, name, section, data, includeIds, excludeIds, trans);
                if (!treeResult.valid) {
                    return treeResult;
                }
            }

            return { valid: true };
        },

        /**
         * Check if entity is in both include and exclude
         */
        checkIncludeExcludeConflict: function(id, name, section, includeIds, excludeIds, trans) {
            if (section === 'include' && excludeIds.indexOf(id) !== -1) {
                return {
                    valid: false,
                    type: 'include_exclude_conflict',
                    error: (trans.error_in_exclude || '"{name}" is already in the exclude list. Remove it from exclude first.').replace('{name}', name)
                };
            }

            if (section === 'exclude' && includeIds.indexOf(id) !== -1) {
                return {
                    valid: false,
                    type: 'include_exclude_conflict',
                    error: (trans.error_in_include || '"{name}" is already in the include list. Remove it from include first.').replace('{name}', name)
                };
            }

            return { valid: true };
        },

        /**
         * Check for redundant selection (already selected)
         */
        checkRedundantSelection: function(id, name, section, includeIds, excludeIds, trans) {
            if (section === 'include' && includeIds.indexOf(id) !== -1) {
                return {
                    valid: false,
                    type: 'redundant',
                    error: (trans.error_already_selected || '"{name}" is already selected.').replace('{name}', name)
                };
            }

            if (section === 'exclude' && excludeIds.indexOf(id) !== -1) {
                return {
                    valid: false,
                    type: 'redundant',
                    error: (trans.error_already_excluded || '"{name}" is already in an exclude list.').replace('{name}', name)
                };
            }

            return { valid: true };
        },

        /**
         * Check for parent-child conflicts in tree entities
         */
        checkTreeConflicts: function(id, name, section, data, includeIds, excludeIds, trans) {
            // Need tree data for parent-child lookups
            if (!this.treeFlatData) {
                return { valid: true };
            }

            var parentId = data && data.parentId ? parseInt(data.parentId, 10) : null;

            // Build lookup for quick access
            var lookup = {};
            this.treeFlatData.forEach(function(item) {
                lookup[parseInt(item.id, 10)] = item;
            });

            // Get all ancestor IDs
            var ancestorIds = this.getAncestorIds(id, lookup);

            // Get all descendant IDs
            var descendantIds = this.getDescendantIds(id, lookup);

            if (section === 'include') {
                // Check if any ancestor is excluded
                for (var i = 0; i < ancestorIds.length; i++) {
                    if (excludeIds.indexOf(ancestorIds[i]) !== -1) {
                        var ancestorName = lookup[ancestorIds[i]] ? lookup[ancestorIds[i]].name : 'Parent';
                        return {
                            valid: false,
                            type: 'parent_excluded',
                            error: (trans.error_parent_excluded || 'Cannot include "{name}" because its parent "{parent}" is excluded.').replace('{name}', name).replace('{parent}', ancestorName)
                        };
                    }
                }

                // Check if any descendant is excluded
                for (var j = 0; j < descendantIds.length; j++) {
                    if (excludeIds.indexOf(descendantIds[j]) !== -1) {
                        var descendantName = lookup[descendantIds[j]] ? lookup[descendantIds[j]].name : 'Child';
                        return {
                            valid: false,
                            type: 'child_excluded',
                            error: (trans.error_child_excluded || 'Cannot include "{name}" because its child "{child}" is excluded. Remove the child from exclude first.').replace('{name}', name).replace('{child}', descendantName)
                        };
                    }
                }
            }

            if (section === 'exclude') {
                // Check if any ancestor is included
                for (var k = 0; k < ancestorIds.length; k++) {
                    if (includeIds.indexOf(ancestorIds[k]) !== -1) {
                        var parentName = lookup[ancestorIds[k]] ? lookup[ancestorIds[k]].name : 'Parent';
                        return {
                            valid: false,
                            type: 'parent_included',
                            error: (trans.error_parent_included || 'Cannot exclude "{name}" because its parent "{parent}" is included. This would create a contradiction.').replace('{name}', name).replace('{parent}', parentName)
                        };
                    }
                }

                // Check if any descendant is included (warning about implicit exclusion)
                var includedDescendants = [];
                for (var m = 0; m < descendantIds.length; m++) {
                    if (includeIds.indexOf(descendantIds[m]) !== -1) {
                        var childName = lookup[descendantIds[m]] ? lookup[descendantIds[m]].name : 'Child';
                        includedDescendants.push(childName);
                    }
                }

                if (includedDescendants.length > 0) {
                    return {
                        valid: false,
                        type: 'children_included',
                        error: (trans.error_children_included || 'Cannot exclude "{name}" because its children ({children}) are included. Remove them from include first.').replace('{name}', name).replace('{children}', includedDescendants.slice(0, 3).join(', ') + (includedDescendants.length > 3 ? '...' : ''))
                    };
                }
            }

            return { valid: true };
        },

        /**
         * Get all ancestor IDs for a given item
         */
        getAncestorIds: function(id, lookup) {
            var ancestors = [];
            var current = lookup[id];

            while (current && current.parent_id) {
                var parentId = parseInt(current.parent_id, 10);
                if (parentId && lookup[parentId]) {
                    ancestors.push(parentId);
                    current = lookup[parentId];
                } else {
                    break;
                }
            }

            return ancestors;
        },

        /**
         * Get all descendant IDs for a given item
         */
        getDescendantIds: function(id, lookup) {
            var descendants = [];
            var self = this;

            // Find direct children
            Object.keys(lookup).forEach(function(key) {
                var item = lookup[key];
                if (parseInt(item.parent_id, 10) === id) {
                    var childId = parseInt(item.id, 10);
                    descendants.push(childId);
                    // Recursively get children's descendants
                    var childDescendants = self.getDescendantIds(childId, lookup);
                    descendants = descendants.concat(childDescendants);
                }
            });

            return descendants;
        },

        /**
         * Get chip IDs from a picker
         */
        getChipIds: function($picker) {
            var ids = [];
            $picker.find('.entity-chip').each(function() {
                ids.push(parseInt($(this).data('id'), 10));
            });
            return ids;
        },

        /**
         * Validate pending selections (for tree view bulk operations)
         * Returns array of invalid items
         */
        validatePendingSelections: function(pendingSelections, section) {
            var self = this;
            var errors = [];

            if (!pendingSelections || !pendingSelections.length) {
                return errors;
            }

            pendingSelections.forEach(function(sel) {
                var result = self.validateSelection(sel.id, sel.name, section, sel.data || {});
                if (!result.valid) {
                    errors.push({
                        id: sel.id,
                        name: sel.name,
                        error: result.error,
                        type: result.type
                    });
                }
            });

            return errors;
        },

        /**
         * Show validation error toast
         */
        showValidationError: function(message) {
            var trans = this.config.trans || {};
            var title = trans.validation_error || 'Selection Conflict';

            // Remove existing toast
            $('.es-validation-toast').remove();

            // Create toast HTML
            var html = '<div class="es-validation-toast">';
            html += '<div class="es-toast-icon">' + this.esIcon('warning') + '</div>';
            html += '<div class="es-toast-content">';
            html += '<div class="es-toast-title">' + this.escapeHtml(title) + '</div>';
            html += '<div class="es-toast-message">' + this.escapeHtml(message) + '</div>';
            html += '</div>';
            html += '<button type="button" class="es-toast-close">' + this.esIcon('close') + '</button>';
            html += '</div>';

            var $toast = $(html);
            $('body').append($toast);

            // Position near dropdown if visible
            if (this.$dropdown && this.$dropdown.hasClass('show')) {
                var dropdownOffset = this.$dropdown.offset();
                $toast.css({
                    position: 'fixed',
                    top: dropdownOffset.top - $toast.outerHeight() - 10,
                    left: dropdownOffset.left,
                    zIndex: 10001
                });
            } else {
                $toast.css({
                    position: 'fixed',
                    top: 20,
                    right: 20,
                    zIndex: 10001
                });
            }

            // Animate in
            $toast.hide().fadeIn(200);

            // Auto-dismiss after 5 seconds
            setTimeout(function() {
                $toast.fadeOut(200, function() {
                    $(this).remove();
                });
            }, 5000);

            // Close button
            $toast.on('click', '.es-toast-close', function() {
                $toast.fadeOut(200, function() {
                    $(this).remove();
                });
            });
        },

        /**
         * Validate and add selection (wrapper that validates before adding)
         * Returns true if added successfully, false if validation failed
         */
        validateAndAddSelection: function($picker, id, name, data, section) {
            var result = this.validateSelection(id, name, section, data || {});

            if (!result.valid) {
                this.showValidationError(result.error);
                return false;
            }

            // Validation passed, add the selection
            this.addSelection($picker, id, name, data);
            return true;
        }
    };

})(jQuery);

/**
 * Entity Selector - Core Module
 * Factory, initialization, state management
 * @partial _core.js
 *
 * IMPORTANT: This file must be loaded LAST in the concatenation order
 * as it combines all mixins from other partials.
 *
 * EXTRACTION SOURCE: assets/js/admin/entity-selector.js
 * Lines: 15-55 (createTargetConditionsInstance, state variables)
 *        56-110 (init method)
 *        108-132 (observeNewSelects)
 *        7889-7951 (Factory object, window export, document ready)
 *
 * Contains:
 * - createTargetConditionsInstance() - Factory function
 * - State variable initialization
 * - init() - Main initialization method
 * - observeNewSelects() - MutationObserver for dynamic selects
 * - loadExistingSelections() - Restore saved state
 * - TargetConditions factory object
 * - window.TargetConditions export
 * - Document ready auto-initialization
 */

(function($) {
    'use strict';

    /**
     * Create a new TargetConditions instance
     * Each instance is independent and manages its own wrapper/state
     */
    function createTargetConditionsInstance() {
        // Base instance object with state variables
        var instance = {
            config: {},
            $wrapper: null,
            $dropdown: null,
            activeGroup: null, // { blockType, groupIndex, section: 'include'|'exclude' }
            searchTimeout: null,
            searchResults: [],
            searchTotal: 0,
            searchOffset: 0,
            searchQuery: '',
            isLoading: false,
            loadMoreCount: 20,
            // Sort, filter, view state
            viewMode: 'list',
            currentSort: { field: 'name', dir: 'ASC' },
            refineQuery: '',
            refineNegate: false,
            filters: {
                inStock: false,
                discounted: false,
                priceMin: null,
                priceMax: null,
                attributes: [],
                features: [],
                // Entity-specific filters
                productCountMin: null,
                productCountMax: null,
                salesMin: null,
                salesMax: null,
                turnoverMin: null,
                turnoverMax: null,
                depth: null,
                hasProducts: false,
                hasDescription: false,
                hasImage: false,
                activeOnly: true, // Default to active only
                attributeGroup: null,
                featureGroup: null,
                dateAddFrom: null,
                dateAddTo: null,
                lastProductFrom: null,
                lastProductTo: null,
                // Country-specific filters
                hasHolidays: false,
                containsStates: false,
                zone: null
            },
            filterableData: null,
            // Search history
            searchHistory: {},
            searchHistoryMax: 10,
            searchHistoryKey: 'targetConditionsSearchHistory',
            // Chips visibility
            maxVisibleChips: 20,
            // Method dropdown references
            $methodDropdownMenu: null,
            $methodDropdownSelect: null,
            $methodDropdownTrigger: null,
            // Preview state
            $previewPopover: null,
            $activeBadge: null,
            $previewList: null,
            previewLoadedCount: 0,
            previewBlockType: null,
            allPreviewData: null,
            // Count update timeout
            countUpdateTimeout: null,

            init: function(options) {
                this.config = $.extend({
                    id: 'target-conditions',
                    name: 'target_conditions',
                    namePrefix: 'target_',
                    mode: 'multi', // Global mode: 'multi' or 'single'
                    blocks: {},
                    ajaxUrl: '',
                    trans: {}
                }, options);

                this.$wrapper = $('[data-entity-selector-id="' + this.config.id + '"]');

                if (!this.$wrapper.length) {
                    return;
                }

                // Global single mode - hide "Add Group" buttons
                if (this.config.mode === 'single') {
                    this.$wrapper.find('.btn-add-group').hide();
                    this.$wrapper.find('.group-excludes').hide();
                    this.$wrapper.find('.group-modifiers').hide();
                }

                // Add fullwidth class to parent form-group (skip for form-group layout)
                var hasLayoutFormGroup = this.$wrapper.hasClass('layout-form-group');
                var $entitySelectorFormGroup = this.$wrapper.closest('.entity-selector-form-group');

                if (!hasLayoutFormGroup && !$entitySelectorFormGroup.length) {
                    var $formGroup = this.$wrapper.closest('.form-group');
                    $formGroup.addClass('condition-trait-fullwidth');
                    $formGroup.find('.col-lg-offset-3').removeClass('col-lg-offset-3');
                }

                this.createDropdown();
                this.bindEvents();
                this.loadExistingSelections();
                this.loadSearchHistory();

                // Initialize styled method dropdowns
                this.initMethodDropdowns();

                // Watch for dynamically added selects
                this.observeNewSelects();

                // Update counts on page load
                var self = this;
                setTimeout(function() {
                    self.updateTabBadges();
                    self.updateAllConditionCounts();
                }, 100);
            },

            observeNewSelects: function() {
                var self = this;

                if (typeof MutationObserver === 'undefined') {
                    return;
                }

                var observer = new MutationObserver(function(mutations) {
                    mutations.forEach(function(mutation) {
                        if (mutation.addedNodes.length) {
                            $(mutation.addedNodes).find('.include-method-select, .exclude-method-select').each(function() {
                                self.enhanceMethodSelect($(this));
                            });
                        }
                    });
                });

                observer.observe(this.$wrapper[0], {
                    childList: true,
                    subtree: true
                });
            },

            loadExistingSelections: function() {
                // TODO: Extract full implementation from original
                // Reads JSON from hidden input and populates chips
            }
        };

        // Merge all mixins into the instance
        // Each mixin adds its methods to window._EntitySelectorMixins
        var mixins = window._EntitySelectorMixins || {};

        // Merge utils mixin
        if (mixins.utils) {
            $.extend(instance, mixins.utils);
        }

        // Merge events mixin
        if (mixins.events) {
            $.extend(instance, mixins.events);
        }

        // Merge dropdown mixin
        if (mixins.dropdown) {
            $.extend(instance, mixins.dropdown);
        }

        // Merge search mixin
        if (mixins.search) {
            $.extend(instance, mixins.search);
        }

        // Merge filters mixin
        if (mixins.filters) {
            $.extend(instance, mixins.filters);
        }

        // Merge chips mixin
        if (mixins.chips) {
            $.extend(instance, mixins.chips);
        }

        // Merge groups mixin
        if (mixins.groups) {
            $.extend(instance, mixins.groups);
        }

        // Merge methods mixin
        if (mixins.methods) {
            $.extend(instance, mixins.methods);
        }

        // Merge preview mixin
        if (mixins.preview) {
            $.extend(instance, mixins.preview);
        }

        // Merge tree mixin
        if (mixins.tree) {
            $.extend(instance, mixins.tree);
        }

        // Merge validation mixin
        if (mixins.validation) {
            $.extend(instance, mixins.validation);
        }

        return instance;
    }

    // Factory object for creating and managing instances
    var TargetConditions = {
        instances: [],

        // Create and initialize a new instance
        create: function(options) {
            var instance = createTargetConditionsInstance();
            instance.init(options);
            this.instances.push(instance);
            return instance;
        },

        // For backwards compatibility - init creates a new instance
        init: function(options) {
            return this.create(options);
        },

        // Validate all instances - returns true if all valid
        validateAll: function() {
            var allValid = true;
            for (var i = 0; i < this.instances.length; i++) {
                if (!this.instances[i].validate()) {
                    allValid = false;
                }
            }
            return allValid;
        }
    };

    // Export to window
    window.TargetConditions = TargetConditions;

    // Auto-initialize on document ready
    $(document).ready(function() {
        // Auto-initialize from data-config attributes on wrapper elements
        $('[data-entity-selector-id]').each(function() {
            var configData = $(this).data('config');
            if (configData) {
                TargetConditions.create(configData);
            }
        });

        // Tips box toggle handler
        $(document).on('click', '.target-tips-box .tips-header', function(e) {
            e.preventDefault();
            $(this).closest('.target-tips-box').toggleClass('expanded');
        });

        // Form submission validation for required target conditions
        $(document).on('submit', 'form', function(e) {
            var $form = $(this);
            if ($form.find('.target-conditions-trait[data-required]').length > 0) {
                if (!TargetConditions.validateAll()) {
                    e.preventDefault();
                    return false;
                }
            }
        });
    });

})(jQuery);
