/**
 * 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);
