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