<?php
/**
 * Entity Selector Renderer
 *
 * Handles HTML rendering for the Entity Selector widget.
 * Extracted from EntitySelector trait.
 *
 * @package MyPrestaRocks\EntitySelector
 */

namespace MyPrestaRocks\EntitySelector\EntitySelector;

if (!defined('_PS_VERSION_')) {
    exit;
}

class EntitySelectorRenderer
{
    /**
     * @var object Parent context (typically the controller using EntitySelector trait)
     */
    protected $context;

    /**
     * @var callable Translation function
     */
    protected $translator;

    /**
     * @var array Current render configuration
     */
    protected $renderConfig = [];

    /**
     * @var array Block definitions
     */
    protected $blockDefinitions = [];

    /**
     * @var string Admin controller name for AJAX URLs
     */
    protected $controllerName = '';

    /**
     * Constructor
     *
     * @param object $context Parent context with module reference
     * @param callable $translator Translation function
     */
    public function __construct($context, callable $translator)
    {
        $this->context = $context;
        $this->translator = $translator;
    }

    // ---------------------------------------------------------------
    // Icon framework abstraction (Material Icons vs FontAwesome 4)
    // ---------------------------------------------------------------

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

    /**
     * FontAwesome 4 class → Material Icons reverse mapping.
     * Built once from $fa4Map on first use.
     * @var array|null
     */
    private static $reverseFa4Map = null;

    /**
     * Extra FA4→Material mappings for icon names used in block configs
     * that don't appear in the standard fa4Map (e.g. icon-cube, icon-folder-o).
     */
    private static $extraReverseMappings = [
        'icon-cube'          => 'inventory',
        'icon-folder-o'      => 'folder',
        'icon-file-text-o'   => 'description',
        'icon-briefcase'     => 'work',
        'icon-user'          => 'person',
        'icon-users'         => 'group',
        'icon-money'         => 'payments',
        'icon-tasks'         => 'checklist',
        'icon-calculator'    => 'calculate',
        'icon-asterisk'      => 'star',
        'icon-bar-chart'     => 'bar_chart',
        'icon-cogs'          => 'settings',
        'icon-cog'           => 'settings',
        'icon-tags'          => 'label',
        'icon-list-ul'       => 'list',
        'icon-th'            => 'grid_view',
        'icon-certificate'   => 'verified',
        'icon-power-off'     => 'power_settings_new',
        'icon-circle-o'      => 'radio_button_unchecked',
    ];

    /**
     * Get the reverse FA4→Material mapping (built lazily from $fa4Map + extras).
     *
     * @return array
     */
    private static function getReverseFa4Map()
    {
        if (self::$reverseFa4Map === null) {
            self::$reverseFa4Map = array_flip(self::$fa4Map);
            // Merge extras (extras take priority for icons not in the flipped map)
            foreach (self::$extraReverseMappings as $fa4Class => $materialName) {
                if (!isset(self::$reverseFa4Map[$fa4Class])) {
                    self::$reverseFa4Map[$fa4Class] = $materialName;
                }
            }
        }
        return self::$reverseFa4Map;
    }

    /**
     * Normalize an icon name to the canonical format for the current mode.
     * Handles both Material Icons names and FA4 class names as input.
     *
     * @param string $name Icon name (Material or FA4 format)
     * @return array ['name' => string, 'extra' => string] normalized name + any extra classes
     */
    protected function normalizeIconName($name)
    {
        $extra = '';

        // If name starts with 'icon-', it's an FA4 class name
        if (strpos($name, 'icon-') === 0) {
            // Extract extra CSS classes (e.g. "icon-power-off text-success" → "icon-power-off" + "text-success")
            $parts = explode(' ', $name, 2);
            $fa4Class = $parts[0];
            if (isset($parts[1])) {
                $extra = $parts[1];
            }

            if ($this->getIconMode() === 'material') {
                // Reverse map FA4→Material
                $reverseMap = self::getReverseFa4Map();
                $materialName = $reverseMap[$fa4Class] ?? null;
                if ($materialName) {
                    return ['name' => $materialName, 'extra' => $extra];
                }
                // Last resort: strip 'icon-' prefix and convert hyphens to underscores
                $fallback = str_replace('-', '_', substr($fa4Class, 5));
                return ['name' => $fallback, 'extra' => $extra];
            }

            // Already FA4 and mode is FA4 — use as-is
            return ['name' => $fa4Class, 'extra' => $extra, 'raw_fa4' => true];
        }

        // Material Icons name — use as-is for material mode, map for FA4 mode
        return ['name' => $name, 'extra' => $extra];
    }

    /**
     * Detect icon mode based on PrestaShop version.
     * PS 8+ / 9+ → material, PS 1.6 / 1.7 → fa4.
     *
     * @return string 'material' or 'fa4'
     */
    protected function getIconMode()
    {
        return version_compare(_PS_VERSION_, '8.0.0', '>=') ? 'material' : 'fa4';
    }

    /**
     * Render an icon element that works on both legacy and modern PS.
     * Accepts both Material Icons names and FA4 class names as input.
     *
     * @param string $name Icon name (Material or FA4 format, e.g. 'shopping_cart' or 'icon-cube')
     * @param string $extraClass Additional CSS class(es)
     * @return string HTML
     */
    protected function renderIcon($name, $extraClass = '')
    {
        $normalized = $this->normalizeIconName($name);
        $iconName = $normalized['name'];
        // Merge extra classes from normalization (e.g. "text-success" from "icon-power-off text-success")
        if (!empty($normalized['extra'])) {
            $extraClass = $extraClass ? $extraClass . ' ' . $normalized['extra'] : $normalized['extra'];
        }

        if ($this->getIconMode() === 'material') {
            $cls = 'material-icons es-icon';
            if ($extraClass) {
                $cls .= ' ' . $extraClass;
            }
            return '<i class="' . $cls . '">' . htmlspecialchars($iconName, ENT_QUOTES, 'UTF-8') . '</i>';
        }

        // FA4 mode
        if (!empty($normalized['raw_fa4'])) {
            // Input was already an FA4 class name — use directly
            $cls = $iconName . ' es-icon';
        } else {
            // Input was a Material name — map to FA4
            $mapped = self::$fa4Map[$iconName] ?? 'icon-circle';
            $cls = $mapped . ' es-icon';
        }
        if ($extraClass) {
            $cls .= ' ' . $extraClass;
        }
        return '<i class="' . $cls . '"></i>';
    }

    /**
     * Set block definitions
     *
     * @param array $blocks Block definitions array
     * @return self
     */
    public function setBlockDefinitions(array $blocks)
    {
        $this->blockDefinitions = $blocks;
        return $this;
    }

    /**
     * Set the controller name for AJAX URLs
     *
     * @param string $controllerName
     * @return self
     */
    public function setControllerName($controllerName)
    {
        $this->controllerName = $controllerName;
        return $this;
    }

    /**
     * Translate a string
     *
     * @param string $string String to translate
     * @return string
     */
    protected function trans($string)
    {
        return call_user_func($this->translator, $string);
    }

    /**
     * Escape HTML for attribute context
     *
     * @param string $string
     * @return string
     */
    protected function escapeAttr($string)
    {
        return htmlspecialchars((string)$string, ENT_QUOTES, 'UTF-8');
    }

    /**
     * Render target conditions HTML
     *
     * @param array $config Widget configuration
     * @param array $savedData Previously saved selection data
     * @return string HTML output
     */
    public function renderEntitySelectorHtml($config = [], $savedData = [])
    {
        $defaults = [
            'id' => 'entity-selector',
            'name' => 'entity_selector',
            'name_prefix' => 'target_',
            'title' => $this->trans('Target Selection'),
            'subtitle' => $this->trans('Define which items to target'),
            'show_products' => true,
            'show_categories' => true,
            'show_manufacturers' => true,
            'show_suppliers' => true,
            'show_cms' => true,
            'show_cms_categories' => true,
            'combination_mode' => 'products',
            'mode' => 'multi',
            'blocks' => [],
            'customBlocks' => [],
            'empty_means_all' => false,
            'required' => false,
            'required_message' => '',
            'show_modifiers' => true,
            'show_all_toggle' => false,
            'layout' => 'standalone', // 'standalone' (default), 'form-group' (full form-group), or 'form-content' (just content for existing form-group)
        ];

        $config = array_merge($defaults, $config);
        $this->renderConfig = $config;

        // Parse saved data
        if (is_string($savedData)) {
            $savedData = json_decode($savedData, true) ?: [];
        }

        // Determine which block is active
        $enabledBlocks = [];
        $activeBlock = null;

        foreach ($this->blockDefinitions as $blockType => $blockDef) {
            $showKey = 'show_' . $blockType;
            if (!empty($config[$showKey])) {
                $enabledBlocks[$blockType] = $blockDef;
                if ($activeBlock === null && !empty($savedData[$blockType]['groups'])) {
                    $activeBlock = $blockType;
                }
            }
        }

        // Add custom blocks if provided
        $customBlocks = $config['customBlocks'] ?? [];
        foreach ($customBlocks as $blockType => $blockDef) {
            $enabledBlocks[$blockType] = array_merge([
                'custom' => true,
                'label' => $blockDef['label'] ?? $blockType,
                'entity_label' => $blockDef['label'] ?? $blockType,
                'entity_label_plural' => $blockDef['label'] ?? $blockType,
                'icon' => $blockDef['icon'] ?? 'settings',
                'search_entity' => $blockType,
                'selection_methods' => [],
            ], $blockDef);

            if ($activeBlock === null && !empty($savedData[$blockType])) {
                $activeBlock = $blockType;
            }
        }

        // Default to first enabled block
        if ($activeBlock === null && !empty($enabledBlocks)) {
            $activeBlock = array_key_first($enabledBlocks);
        }

        // Check if any block has data
        $hasAnyData = false;
        foreach ($enabledBlocks as $blockType => $blockDef) {
            if (!empty($savedData[$blockType]['groups'])) {
                $hasAnyData = true;
                break;
            }
        }

        // Determine collapsed state
        $collapsedClass = $hasAnyData ? '' : ' collapsed';
        $globalMode = $config['mode'] ?? 'multi';
        $singleModeClass = $globalMode === 'single' ? ' single-mode' : '';

        // Generate JS config
        $jsConfig = $this->getJsConfig($config, $enabledBlocks);
        $jsConfigJson = $this->escapeAttr(json_encode($jsConfig));

        $requiredClass = !empty($config['required']) ? ' trait-required' : '';
        $layout = $config['layout'] ?? 'standalone';
        $layoutClass = $layout === 'form-group' ? ' layout-form-group' : '';

        // Form-group layout: wrap in PrestaShop form structure
        if ($layout === 'form-group') {
            return $this->renderFormGroupLayout($config, $enabledBlocks, $activeBlock, $savedData, $globalMode, $jsConfigJson);
        }

        // Form-content layout: just content for existing form-group (no wrapper, no extra label)
        if ($layout === 'form-content') {
            return $this->renderFormContentLayout($config, $enabledBlocks, $activeBlock, $savedData, $globalMode, $jsConfigJson);
        }

        // Standalone layout (default)
        $html = '<div class="condition-trait target-conditions-trait' . $collapsedClass . $singleModeClass . $requiredClass . $layoutClass . '"';
        $html .= ' data-entity-selector-id="' . $this->escapeAttr($config['id']) . '"';
        $html .= ' data-mode="' . $this->escapeAttr($globalMode) . '"';
        $html .= ' data-icon-mode="' . $this->getIconMode() . '"';
        if (!empty($config['required'])) {
            $html .= ' data-required="1"';
            $requiredMsg = !empty($config['required_message'])
                ? $config['required_message']
                : $this->trans('Please select at least one item');
            $html .= ' data-required-message="' . $this->escapeAttr($requiredMsg) . '"';
        }
        $html .= ' data-config=\'' . $jsConfigJson . '\'>';

        // Header
        $html .= $this->renderHeader($config, $hasAnyData, $globalMode);

        // Body
        $bodyStyle = !empty($collapsedClass) ? ' style="display:none;"' : '';
        $html .= '<div class="condition-trait-body"' . $bodyStyle . '>';

        // Tabs
        $html .= $this->renderTabs($enabledBlocks, $activeBlock, $savedData, $config);

        // Blocks
        $html .= $this->renderBlocks($enabledBlocks, $activeBlock, $savedData, $config, $globalMode);

        // Tips box
        $html .= $this->renderTipsBox();

        // Hidden input
        $html .= '<input type="hidden" name="' . $this->escapeAttr($config['name']) . '" value="' . $this->escapeAttr(json_encode($savedData)) . '">';

        $html .= '</div>'; // End condition-trait-body

        $html .= '</div>'; // End target-conditions-trait

        return $html;
    }

    /**
     * Render form-group layout (matches standard PrestaShop form fields)
     *
     * @param array $config Configuration
     * @param array $enabledBlocks Enabled blocks
     * @param string $activeBlock Active block type
     * @param array $savedData Saved data
     * @param string $globalMode Selection mode
     * @param string $jsConfigJson JSON-encoded JS config
     * @return string HTML
     */
    protected function renderFormGroupLayout($config, $enabledBlocks, $activeBlock, $savedData, $globalMode, $jsConfigJson)
    {
        $requiredClass = !empty($config['required']) ? ' trait-required' : '';
        $singleModeClass = $globalMode === 'single' ? ' single-mode' : '';

        $html = '<div class="form-group entity-selector-form-group">';

        // Label column (col-lg-3) - standard PrestaShop form field markup
        $html .= '<label class="control-label col-lg-3">';
        $html .= '<span class="mpr-label-text">' . $this->escapeAttr($config['title']) . '</span>';
        if (!empty($config['required'])) {
            $html .= ' <span class="text-danger">*</span>';
        }
        $html .= '</label>';

        // Content column (col-lg-9)
        $html .= '<div class="col-lg-9">';

        // Entity selector container (without traditional header)
        $html .= '<div class="condition-trait target-conditions-trait layout-form-group' . $singleModeClass . $requiredClass . '"';
        $html .= ' data-entity-selector-id="' . $this->escapeAttr($config['id']) . '"';
        $html .= ' data-mode="' . $this->escapeAttr($globalMode) . '"';
        $html .= ' data-icon-mode="' . $this->getIconMode() . '"';
        if (!empty($config['required'])) {
            $html .= ' data-required="1"';
            $requiredMsg = !empty($config['required_message'])
                ? $config['required_message']
                : $this->trans('Please select at least one item');
            $html .= ' data-required-message="' . $this->escapeAttr($requiredMsg) . '"';
        }
        $html .= ' data-config=\'' . $jsConfigJson . '\'>';

        // Body (always visible in form-group layout)
        $html .= '<div class="condition-trait-body">';

        // Tabs row with expand button
        $html .= '<div class="entity-selector-tabs-row">';
        $html .= $this->renderTabs($enabledBlocks, $activeBlock, $savedData, $config);

        // Expand/collapse button for groups (multi mode only)
        if ($globalMode !== 'single') {
            $html .= '<div class="entity-selector-actions">';
            $html .= '<button type="button" class="btn-toggle-groups" data-state="collapsed" title="' . $this->trans('Expand all groups') . '">';
            $html .= $this->renderIcon('open_in_full');
            $html .= '</button>';
            $html .= '</div>';
        }
        $html .= '</div>'; // End tabs-row

        // Blocks
        $html .= $this->renderBlocks($enabledBlocks, $activeBlock, $savedData, $config, $globalMode);

        // Tips box
        $html .= $this->renderTipsBox();

        // Hidden input
        $html .= '<input type="hidden" name="' . $this->escapeAttr($config['name']) . '" value="' . $this->escapeAttr(json_encode($savedData)) . '">';

        $html .= '</div>'; // End condition-trait-body

        $html .= '</div>'; // End target-conditions-trait

        // Subtitle as help text
        if (!empty($config['subtitle'])) {
            $html .= '<small class="form-text text-muted">' . $this->escapeAttr($config['subtitle']) . '</small>';
        }

        $html .= '</div>'; // End col-lg-9
        $html .= '</div>'; // End form-group

        return $html;
    }

    /**
     * Render form-content layout (just content, no form-group wrapper)
     * Use this when entity selector is inside an existing PrestaShop form type='html' field
     *
     * @param array $config Configuration
     * @param array $enabledBlocks Enabled blocks
     * @param string $activeBlock Active block type
     * @param array $savedData Saved data
     * @param string $globalMode Selection mode
     * @param string $jsConfigJson JSON-encoded JS config
     * @return string HTML
     */
    protected function renderFormContentLayout($config, $enabledBlocks, $activeBlock, $savedData, $globalMode, $jsConfigJson)
    {
        $requiredClass = !empty($config['required']) ? ' trait-required' : '';
        $singleModeClass = $globalMode === 'single' ? ' single-mode' : '';

        // Default: collapsed (true) — shows clean tab bar; users click to expand
        $collapsed = isset($config['collapsed']) ? $config['collapsed'] : true;
        $collapsedClass = $collapsed ? ' blocks-collapsed' : '';

        // Entity selector container (without form-group wrapper, without header)
        $html = '<div class="condition-trait target-conditions-trait layout-form-group' . $singleModeClass . $requiredClass . $collapsedClass . '"';
        $html .= ' data-entity-selector-id="' . $this->escapeAttr($config['id']) . '"';
        $html .= ' data-mode="' . $this->escapeAttr($globalMode) . '"';
        $html .= ' data-icon-mode="' . $this->getIconMode() . '"';
        if (!empty($config['required'])) {
            $html .= ' data-required="1"';
            $requiredMsg = !empty($config['required_message'])
                ? $config['required_message']
                : $this->trans('Please select at least one item');
            $html .= ' data-required-message="' . $this->escapeAttr($requiredMsg) . '"';
        }
        $html .= ' data-config=\'' . $jsConfigJson . '\'>';

        // Tabs row (always visible) with collapse toggle
        $html .= '<div class="entity-selector-tabs-row">';
        $html .= $this->renderTabs($enabledBlocks, $activeBlock, $savedData, $config);

        // Actions: expand/collapse toggle (entire area is clickable)
        $html .= '<div class="entity-selector-actions btn-toggle-blocks" title="' . $this->trans('Show/hide details') . '">';
        $html .= $this->renderIcon($collapsed ? 'expand_more' : 'expand_less');
        $html .= '</div>';
        $html .= '</div>'; // End tabs-row

        // Blocks content (visible by default when not collapsed)
        $blocksStyle = $collapsed ? ' style="display:none;"' : '';
        $html .= '<div class="entity-selector-blocks-content"' . $blocksStyle . '>';

        // Blocks
        $html .= $this->renderBlocks($enabledBlocks, $activeBlock, $savedData, $config, $globalMode);

        // Tips box
        $html .= $this->renderTipsBox();

        $html .= '</div>'; // End blocks-content

        // Hidden input (outside collapsed area)
        $html .= '<input type="hidden" name="' . $this->escapeAttr($config['name']) . '" value="' . $this->escapeAttr(json_encode($savedData)) . '">';


        $html .= '</div>'; // End target-conditions-trait

        return $html;
    }

    /**
     * Render widget header
     *
     * @param array $config Configuration
     * @param bool $hasAnyData Whether any data exists
     * @param string $globalMode Selection mode
     * @return string HTML
     */
    protected function renderHeader($config, $hasAnyData, $globalMode)
    {
        $html = '<div class="condition-trait-header">';
        $html .= '<div class="trait-header-left">';
        $html .= $this->renderIcon('my_location', 'trait-icon');
        $html .= '<div class="trait-title-group">';
        $html .= '<span class="trait-title">' . $this->escapeAttr($config['title']) . '</span>';
        $html .= '<span class="trait-subtitle">' . $this->escapeAttr($config['subtitle']) . '</span>';
        $html .= '</div>';
        $html .= '<span class="trait-total-count" style="display: none;" title="' . $this->trans('Total items targeted') . '">' . $this->renderIcon('visibility') . ' <span class="count-value"></span></span>';
        $html .= '</div>';
        $html .= '<div class="trait-header-right">';

        // Toggle switch
        $showToggle = $config['show_all_toggle'] ?? false;
        $emptyMeansAllToggle = $config['empty_means_all'] ?? false;
        if ($showToggle && $globalMode !== 'single') {
            $html .= $this->renderToggleSwitch($config, $hasAnyData, $emptyMeansAllToggle);
        }

        // Collapse/expand toggle
        if ($globalMode !== 'single') {
            $html .= '<div class="trait-header-actions">';
            $html .= '<button type="button" class="btn-toggle-groups" data-state="collapsed" title="' . $this->trans('Expand all groups') . '">';
            $html .= $this->renderIcon('open_in_full');
            $html .= '</button>';
            $html .= '</div>';
        }
        $html .= '<span class="collapse-icon"></span>';
        $html .= '</div>';
        $html .= '</div>';

        return $html;
    }

    /**
     * Render toggle switch
     *
     * @param array $config Configuration
     * @param bool $hasAnyData Whether data exists
     * @param bool $emptyMeansAllToggle Empty means all setting
     * @return string HTML
     */
    protected function renderToggleSwitch($config, $hasAnyData, $emptyMeansAllToggle)
    {
        $showToggle = $config['show_all_toggle'];

        if (is_array($showToggle)) {
            if (isset($showToggle['selected']) || isset($showToggle['empty'])) {
                $selectedLabel = $showToggle['selected'] ?? $this->trans('Targeted');
                $emptyLabel = $showToggle['empty'] ?? ($emptyMeansAllToggle ? $this->trans('Everyone') : $this->trans('None'));
            } else {
                $emptyLabel = $showToggle['on'] ?? ($emptyMeansAllToggle ? $this->trans('Everyone') : $this->trans('None'));
                $selectedLabel = $showToggle['off'] ?? $this->trans('Targeted');
            }
        } else {
            $selectedLabel = $this->trans('Targeted');
            $emptyLabel = $emptyMeansAllToggle ? $this->trans('Everyone') : $this->trans('None');
        }

        $switchName = 'target_switch_' . $config['name'];

        $html = '<span class="switch prestashop-switch fixed-width-xl" onclick="event.stopPropagation();">';

        if ($emptyMeansAllToggle) {
            $html .= '<input type="radio" name="' . $switchName . '" id="' . $switchName . '_on" value="1"';
            $html .= ' class="target-switch-toggle"' . (!$hasAnyData ? ' checked' : '') . '>';
            $html .= '<label for="' . $switchName . '_on">' . $this->escapeAttr($emptyLabel) . '</label>';
            $html .= '<input type="radio" name="' . $switchName . '" id="' . $switchName . '_off" value="0"';
            $html .= ' class="target-switch-toggle"' . ($hasAnyData ? ' checked' : '') . '>';
            $html .= '<label for="' . $switchName . '_off">' . $this->escapeAttr($selectedLabel) . '</label>';
        } else {
            $html .= '<input type="radio" name="' . $switchName . '" id="' . $switchName . '_on" value="0"';
            $html .= ' class="target-switch-toggle"' . ($hasAnyData ? ' checked' : '') . '>';
            $html .= '<label for="' . $switchName . '_on">' . $this->escapeAttr($selectedLabel) . '</label>';
            $html .= '<input type="radio" name="' . $switchName . '" id="' . $switchName . '_off" value="1"';
            $html .= ' class="target-switch-toggle"' . (!$hasAnyData ? ' checked' : '') . '>';
            $html .= '<label for="' . $switchName . '_off">' . $this->escapeAttr($emptyLabel) . '</label>';
        }

        $html .= '<a class="slide-button btn"></a>';
        $html .= '</span>';

        return $html;
    }

    /**
     * Render block tabs
     *
     * @param array $enabledBlocks Enabled blocks
     * @param string $activeBlock Active block type
     * @param array $savedData Saved data
     * @param array $config Configuration
     * @return string HTML
     */
    protected function renderTabs($enabledBlocks, $activeBlock, $savedData, $config)
    {
        $blockSettings = $config['blocks'] ?? [];
        $html = '<div class="target-block-tabs">';

        foreach ($enabledBlocks as $blockType => $blockDef) {
            $isActive = ($blockType === $activeBlock);
            $hasData = !empty($savedData[$blockType]['groups']);
            $activeClass = $isActive ? ' active' : '';
            $hasDataClass = $hasData ? ' has-data' : '';
            $blockMode = $blockSettings[$blockType]['mode'] ?? 'multi';

            $html .= '<button type="button" class="target-block-tab' . $activeClass . $hasDataClass . '" data-block-type="' . $this->escapeAttr($blockType) . '" data-block-mode="' . $this->escapeAttr($blockMode) . '">';
            $html .= $this->renderIcon($blockDef['icon']);
            $html .= '<span class="tab-label">' . $this->escapeAttr($blockDef['label']) . '</span>';
            if ($hasData) {
                // Show loading spinner that will be replaced with actual count
                $html .= '<span class="tab-badge loading">' . $this->renderIcon('sync', 'es-spin') . '</span>';
            }
            $html .= '</button>';
        }
        $html .= '</div>';

        return $html;
    }

    /**
     * Render all enabled blocks
     *
     * @param array $enabledBlocks Enabled blocks
     * @param string $activeBlock Active block type
     * @param array $savedData Saved data
     * @param array $config Configuration
     * @param string $globalMode Selection mode
     * @return string HTML
     */
    protected function renderBlocks($enabledBlocks, $activeBlock, $savedData, $config, $globalMode)
    {
        $blockSettings = $config['blocks'] ?? [];
        $emptyMeansAll = $config['empty_means_all'] ?? true;
        $html = '<div class="target-blocks-wrapper">';

        foreach ($enabledBlocks as $blockType => $blockDef) {
            $isActive = ($blockType === $activeBlock);
            $blockData = $savedData[$blockType] ?? [];
            $blockMode = ($globalMode === 'single') ? 'single' : ($blockSettings[$blockType]['mode'] ?? 'multi');
            $html .= $this->renderTargetBlock($blockType, $blockDef, $blockData, $isActive, $blockMode, $emptyMeansAll);
        }

        $html .= '</div>';
        return $html;
    }

    /**
     * Render tips box
     *
     * @return string HTML
     */
    protected function renderTipsBox()
    {
        $html = '<div class="target-tips-box">';
        $html .= '<div class="tips-header">';
        $html .= $this->renderIcon('lightbulb');
        $html .= '<span>' . $this->trans('Pro Tips: Combine include & exclude for powerful targeting') . '</span>';
        $html .= $this->renderIcon('expand_more', 'tips-toggle');
        $html .= '</div>';
        $html .= '<div class="tips-content">';
        $html .= '<div class="tips-grid">';

        // Example 1
        $html .= '<div class="tip-item">';
        $html .= '<div class="tip-icon">' . $this->renderIcon('check_circle') . '</div>';
        $html .= '<div class="tip-text">';
        $html .= '<strong>' . $this->trans('Target entire catalog with exceptions') . '</strong>';
        $html .= '<p>' . $this->trans('Select "All products", then exclude specific categories like "Sale" or "Clearance" where you don\'t want the rule to apply.') . '</p>';
        $html .= '</div>';
        $html .= '</div>';

        // Example 2
        $html .= '<div class="tip-item">';
        $html .= '<div class="tip-icon">' . $this->renderIcon('filter_list') . '</div>';
        $html .= '<div class="tip-text">';
        $html .= '<strong>' . $this->trans('Combine features for precise filtering') . '</strong>';
        $html .= '<p>' . $this->trans('Target all "Cotton" products, then exclude those with "Black" color feature. Perfect for material-specific promotions.') . '</p>';
        $html .= '</div>';
        $html .= '</div>';

        // Example 3
        $html .= '<div class="tip-item">';
        $html .= '<div class="tip-icon">' . $this->renderIcon('account_tree') . '</div>';
        $html .= '<div class="tip-text">';
        $html .= '<strong>' . $this->trans('Category-based targeting') . '</strong>';
        $html .= '<p>' . $this->trans('Include entire "Men\'s Clothing" category, exclude "Accessories" subcategory. Hierarchy is respected automatically.') . '</p>';
        $html .= '</div>';
        $html .= '</div>';

        // Example 4
        $html .= '<div class="tip-item">';
        $html .= '<div class="tip-icon">' . $this->renderIcon('business') . '</div>';
        $html .= '<div class="tip-text">';
        $html .= '<strong>' . $this->trans('Brand exclusions') . '</strong>';
        $html .= '<p>' . $this->trans('Target all products from "Nike" manufacturer, but exclude items already on sale (by price range or specific products).') . '</p>';
        $html .= '</div>';
        $html .= '</div>';

        $html .= '</div>'; // End tips-grid
        $html .= '<div class="tips-footer">';
        $html .= $this->renderIcon('info') . ' ';
        $html .= $this->trans('Multiple groups work as OR logic. Items matching ANY group are included (unless explicitly excluded in that group).');
        $html .= '</div>';
        $html .= '</div>'; // End tips-content
        $html .= '</div>'; // End target-tips-box

        return $html;
    }

    /**
     * Render a single target block
     *
     * @param string $blockType Block type key
     * @param array $blockDef Block definition
     * @param array $blockData Block saved data
     * @param bool $isActive Whether block tab is active
     * @param string $mode Selection mode
     * @param bool $emptyMeansAll Empty interpretation
     * @return string HTML
     */
    public function renderTargetBlock($blockType, $blockDef, $blockData, $isActive = true, $mode = 'multi', $emptyMeansAll = true)
    {
        $groups = $blockData['groups'] ?? [];
        $hasGroups = !empty($groups);
        $displayStyle = $isActive ? '' : ' style="display:none;"';
        $activeClass = $isActive ? ' active' : '';
        $modeClass = $mode === 'single' ? ' mode-single' : '';
        $isCustomBlock = !empty($blockDef['custom']);
        $customClass = $isCustomBlock ? ' custom-block' : '';

        $html = '<div class="target-block' . $activeClass . $modeClass . $customClass . '" data-block-type="' . $this->escapeAttr($blockType) . '" data-mode="' . $this->escapeAttr($mode) . '"' . $displayStyle . '>';
        $html .= '<div class="block-body">';

        // Custom blocks render their own HTML
        if ($isCustomBlock && !empty($blockDef['html'])) {
            $html .= '<div class="custom-block-content">';
            $html .= $blockDef['html'];
            $html .= '</div>';
            $html .= '</div></div>';
            return $html;
        }

        // Groups container
        $html .= '<div class="groups-container">';

        $emptyStateText = $this->getEmptyStateText($blockDef, $mode, $emptyMeansAll);
        $methods = $blockDef['selection_methods'] ?? [];

        if ($hasGroups) {
            $renderedGroups = 0;
            $showEmptyGroups = $this->renderConfig['show_empty_groups'] ?? false;
            foreach ($groups as $groupIndex => $group) {
                $includeMethod = $group['include']['method'] ?? 'all';
                $includeValues = $group['include']['values'] ?? [];
                // Skip validation if show_empty_groups is enabled (for pre-populated empty groups)
                if (!$showEmptyGroups && !$this->isConditionValid($includeMethod, $includeValues, $methods)) {
                    continue;
                }
                if ($mode === 'single' && $renderedGroups > 0) {
                    break;
                }
                $html .= $this->renderTargetGroup($blockType, $blockDef, $group, $renderedGroups, $mode);
                $renderedGroups++;
            }
            if ($renderedGroups === 0) {
                $html .= '<div class="groups-empty-state">';
                $html .= '<span class="empty-state-text">' . $emptyStateText . '</span>';
                $html .= '</div>';
            }
        } else {
            if ($mode === 'single') {
                $defaultGroup = [
                    'include' => [
                        'method' => 'specific',
                        'values' => [],
                    ],
                ];
                $html .= $this->renderTargetGroup($blockType, $blockDef, $defaultGroup, 0, $mode);
            } else {
                $html .= '<div class="groups-empty-state">';
                $html .= '<span class="empty-state-text">' . $emptyStateText . '</span>';
                $html .= '</div>';
            }
        }

        $html .= '</div>'; // End groups-container

        // Add group button (multi mode only)
        if ($mode !== 'single') {
            $groupsTooltip = '<strong>' . $this->trans('Selection Groups') . '</strong>';
            $groupsTooltip .= '<p>' . $this->trans('Groups let you combine multiple selection criteria:') . '</p>';
            $groupsTooltip .= '<div class="tooltip-list">';
            $groupsTooltip .= '<div><strong>' . $this->trans('Include') . ':</strong> ' . $this->trans('Choose which items to select (by category, manufacturer, pattern, etc.)') . '</div>';
            $groupsTooltip .= '<div><strong>' . $this->trans('Exclude') . ':</strong> ' . $this->trans('Remove specific items from your selection') . '</div>';
            $groupsTooltip .= '</div>';
            $groupsTooltip .= '<p class="tooltip-logic"><strong>' . $this->trans('Logic:') . '</strong> ' . $this->trans('Multiple groups are combined with OR. Within a group: Include MINUS Exclude.') . '</p>';
            $groupsTooltip .= '<p class="tooltip-example">' . $this->trans('Example: Group 1 selects "Clothing" category, Group 2 selects "Nike" manufacturer = all Clothing OR all Nike products') . '</p>';

            $html .= '<div class="block-footer">';
            $html .= '<button type="button" class="btn-add-group">';
            $html .= $this->renderIcon('add') . ' ' . $this->trans('Add selection group');
            $html .= '</button>';
            $html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($groupsTooltip) . '">';
            $html .= $this->renderIcon('info');
            $html .= '</span>';
            $html .= '</div>';
        }

        $html .= '</div>'; // End block-body
        $html .= '</div>'; // End target-block

        return $html;
    }

    /**
     * Get empty state text based on mode and emptyMeansAll setting
     *
     * @param array $blockDef Block definition
     * @param string $mode Selection mode
     * @param bool $emptyMeansAll Empty interpretation
     * @return string
     */
    protected function getEmptyStateText($blockDef, $mode, $emptyMeansAll)
    {
        if ($mode === 'single') {
            return $this->trans('No item selected');
        }

        if ($emptyMeansAll) {
            return $this->trans('All') . ' ' . $this->escapeAttr($blockDef['entity_label_plural'] ?? 'items') . ' ' . $this->trans('included');
        }

        return $this->trans('Nothing selected');
    }

    /**
     * Render result modifiers section (limit, sort)
     *
     * @param string $blockType Block type
     * @param array $blockData Block data
     * @return string HTML
     */
    public function renderResultModifiers($blockType, $blockData)
    {
        $modifiers = $blockData['modifiers'] ?? [];
        $limit = $modifiers['limit'] ?? '';
        $sortBy = $modifiers['sort_by'] ?? '';
        $sortDir = $modifiers['sort_dir'] ?? 'DESC';

        $sortOptions = $this->getResultModifierSortOptions($blockType);

        $modifiersTooltip = '<strong>' . $this->trans('Result Modifiers') . '</strong>';
        $modifiersTooltip .= '<p>' . $this->trans('Apply final adjustments to items matched by the selection groups above:') . '</p>';
        $modifiersTooltip .= '<div class="tooltip-list">';
        $modifiersTooltip .= '<div><strong>' . $this->trans('Limit') . ':</strong> ' . $this->trans('Show only the top N items from your selection') . '</div>';
        $modifiersTooltip .= '<div><strong>' . $this->trans('Sort by') . ':</strong> ' . $this->trans('Reorder items before applying the limit') . '</div>';
        $modifiersTooltip .= '</div>';
        $modifiersTooltip .= '<p class="tooltip-example">' . $this->trans('Example: Limit to 5, sorted by Best sellers = show the top 5 best-selling items from your selection above') . '</p>';

        $html = '<div class="result-modifiers-section">';
        $html .= '<div class="result-modifiers-header">';
        $html .= $this->renderIcon('tune') . ' ';
        $html .= '<span class="result-modifiers-title">' . $this->trans('Result modifiers') . '</span>';
        $html .= '<span class="result-modifiers-hint">' . $this->trans('(optional)') . '</span>';
        $html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($modifiersTooltip) . '">';
        $html .= $this->renderIcon('info');
        $html .= '</span>';
        $html .= '</div>';

        $html .= '<div class="result-modifiers-content">';

        // Limit input
        $html .= '<div class="modifier-group">';
        $html .= '<label class="modifier-label">' . $this->trans('Limit to') . '</label>';
        $html .= '<input type="number" class="modifier-limit-input" value="' . $this->escapeAttr($limit) . '" placeholder="' . $this->trans('All') . '" min="1" step="1">';
        $html .= '<span class="modifier-suffix">' . $this->trans('items') . '</span>';
        $html .= '</div>';

        // Sort by dropdown
        $html .= '<div class="modifier-group">';
        $html .= '<label class="modifier-label">' . $this->trans('Sort by') . '</label>';
        $html .= '<select class="modifier-sort-select">';
        $html .= '<option value="">' . $this->trans('Default order') . '</option>';
        foreach ($sortOptions as $optKey => $optLabel) {
            $selected = ($sortBy === $optKey) ? ' selected' : '';
            $html .= '<option value="' . $this->escapeAttr($optKey) . '"' . $selected . '>' . $this->escapeAttr($optLabel) . '</option>';
        }
        $html .= '</select>';

        // Sort direction
        $html .= '<select class="modifier-sort-dir-select"' . (empty($sortBy) ? ' style="display:none;"' : '') . '>';
        $html .= '<option value="DESC"' . ($sortDir === 'DESC' ? ' selected' : '') . '>' . $this->trans('Descending') . '</option>';
        $html .= '<option value="ASC"' . ($sortDir === 'ASC' ? ' selected' : '') . '>' . $this->trans('Ascending') . '</option>';
        $html .= '</select>';
        $html .= '</div>';

        $html .= '</div>'; // End result-modifiers-content
        $html .= '</div>'; // End result-modifiers-section

        return $html;
    }

    /**
     * Get sort options for result modifiers
     *
     * @param string $blockType Block type
     * @return array
     */
    protected function getResultModifierSortOptions($blockType)
    {
        switch ($blockType) {
            case 'products':
                return [
                    'sales' => $this->trans('Best sellers'),
                    'date_add' => $this->trans('Newest'),
                    'price' => $this->trans('Price'),
                    'name' => $this->trans('Name'),
                    'position' => $this->trans('Position'),
                    'quantity' => $this->trans('Stock quantity'),
                    'random' => $this->trans('Random'),
                ];

            case 'categories':
                return [
                    'name' => $this->trans('Name'),
                    'position' => $this->trans('Position'),
                    'product_count' => $this->trans('Product count'),
                    'total_sales' => $this->trans('Best sellers (by product sales)'),
                    'newest_products' => $this->trans('Newest products (by latest product)'),
                    'date_add' => $this->trans('Category creation date'),
                    'random' => $this->trans('Random'),
                ];

            case 'manufacturers':
            case 'suppliers':
                return [
                    'name' => $this->trans('Name'),
                    'product_count' => $this->trans('Product count'),
                    'total_sales' => $this->trans('Best sellers (by product sales)'),
                    'newest_products' => $this->trans('Newest products (by latest product)'),
                    'random' => $this->trans('Random'),
                ];

            case 'cms':
            case 'cms_categories':
                return [
                    'name' => $this->trans('Name'),
                    'position' => $this->trans('Position'),
                    'random' => $this->trans('Random'),
                ];

            default:
                return [
                    'name' => $this->trans('Name'),
                    'random' => $this->trans('Random'),
                ];
        }
    }

    /**
     * Render a selection group
     *
     * @param string $blockType Block type
     * @param array $blockDef Block definition
     * @param array $group Group data
     * @param int $groupIndex Group index
     * @param string $mode Selection mode
     * @return string HTML
     */
    public function renderTargetGroup($blockType, $blockDef, $group, $groupIndex, $mode = 'multi')
    {
        $methods = $blockDef['selection_methods'] ?? [];

        $includeMethod = $group['include']['method'] ?? 'all';
        $includeValues = $group['include']['values'] ?? [];

        // Support both old (single exclude) and new (multiple excludes) format
        $excludes = [];
        if (!empty($group['excludes']) && is_array($group['excludes'])) {
            $excludes = $group['excludes'];
        } elseif (!empty($group['exclude']['method'])) {
            $excludes = [$group['exclude']];
        }

        // Filter out invalid excludes
        $excludes = array_filter($excludes, function ($exclude) use ($methods) {
            $method = $exclude['method'] ?? '';
            $values = $exclude['values'] ?? [];
            return !empty($method) && $this->isConditionValid($method, $values, $methods);
        });
        $excludes = array_values($excludes);
        $hasExcludes = !empty($excludes) && $mode !== 'single';

        $hasGroupData = !empty($includeValues) || !empty($excludes);
        $groupCollapsedClass = ($hasGroupData && $mode !== 'single') ? ' collapsed' : '';

        $groupName = isset($group['name']) ? $this->escapeAttr($group['name']) : '';
        $defaultGroupName = $this->trans('Group') . ' ' . ($groupIndex + 1);

        $html = '<div class="selection-group' . $groupCollapsedClass . '" data-group-index="' . $groupIndex . '" data-group-name="' . $groupName . '">';

        // Group header
        if ($mode === 'single') {
            $html .= '<div class="group-header group-header-single">';
            $html .= '<span class="group-count-badge" style="display:none;">' . $this->renderIcon('sync', 'es-spin') . '</span>';
            $html .= '</div>';
        } else {
            $html .= '<div class="group-header">';
            $html .= '<span class="group-collapse-toggle">' . $this->renderIcon('expand_less') . '</span>';
            $html .= '<span class="group-name-wrapper">';
            $html .= '<input type="text" class="group-name-input" value="' . $groupName . '" placeholder="' . $defaultGroupName . '" title="' . $this->trans('Click to name this group') . '">';
            $html .= '<span class="group-count-badge" style="display:none;">' . $this->renderIcon('sync', 'es-spin') . '</span>';
            $html .= '</span>';
            $html .= '<button type="button" class="btn-remove-group" title="' . $this->trans('Remove group') . '">';
            $html .= $this->renderIcon('delete');
            $html .= '</button>';
            $html .= '</div>';
        }

        // Group body
        $html .= '<div class="group-body">';

        // Include section
        $html .= '<div class="group-include">';
        $html .= '<div class="section-row">';

        // Method selector
        $html .= '<div class="method-selector-wrapper">';
        $methodHelp = $this->getMethodHelpTooltip($includeMethod, $blockType);
        $html .= '<span class="method-info-placeholder">';
        if (!empty($methodHelp)) {
            $html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($methodHelp) . '">';
            $html .= $this->renderIcon('info');
            $html .= '</span>';
        }
        $html .= '</span>';
        $html .= '<select class="include-method-select">';
        $html .= $this->renderMethodOptions($methods, $includeMethod, false);
        $html .= '</select>';
        $html .= '<span class="condition-match-count no-matches">' . $this->renderIcon('visibility') . ' <span class="preview-count">0</span></span>';
        $html .= '</div>';

        // Value picker
        $html .= $this->renderValuePicker('include', $methods, $includeMethod, $includeValues);

        $html .= '</div>'; // End section-row
        $html .= '</div>'; // End group-include

        // Excludes section (multi mode only)
        if ($mode !== 'single') {
            $html .= '<div class="group-excludes' . ($hasExcludes ? ' has-excludes' : '') . '">';

            if ($hasExcludes) {
                $html .= '<div class="except-separator">';
                $html .= '<span class="except-label">' . $this->renderIcon('block') . ' ' . $this->trans('EXCEPT') . '</span>';
                $html .= '</div>';

                $html .= '<div class="exclude-rows-container">';
                foreach ($excludes as $excludeIndex => $exclude) {
                    $html .= $this->renderExcludeRow($blockType, $methods, $exclude, $excludeIndex);
                }
                $html .= '</div>';

                $html .= '<button type="button" class="btn-add-another-exclude">';
                $html .= $this->renderIcon('add') . ' ' . $this->trans('Add another exception');
                $html .= '</button>';
            } else {
                $html .= '<button type="button" class="btn-add-exclude">';
                $html .= $this->renderIcon('add') . ' ' . $this->trans('Add exceptions');
                $html .= '</button>';
            }

            $html .= '</div>'; // End group-excludes

            // Group modifiers
            $html .= $this->renderGroupModifiers($blockType, $group);
        }

        $html .= '</div>'; // End group-body
        $html .= '</div>'; // End selection-group

        return $html;
    }

    /**
     * Render group modifiers (limit, sort per group)
     *
     * @param string $blockType Block type
     * @param array $group Group data
     * @return string HTML
     */
    public function renderGroupModifiers($blockType, $group)
    {
        $showModifiers = $this->renderConfig['show_modifiers'] ?? true;
        if (!$showModifiers) {
            return '';
        }

        $modifiers = $group['modifiers'] ?? [];
        $limit = $modifiers['limit'] ?? '';
        $sortBy = $modifiers['sort_by'] ?? '';
        $sortDir = $modifiers['sort_dir'] ?? 'DESC';

        $sortOptions = $this->getResultModifierSortOptions($blockType);

        if (empty($sortBy)) {
            $sortBy = 'sales';
        }

        $html = '<div class="group-modifiers">';

        // Limit
        $html .= '<span class="modifier-inline modifier-limit">';
        $html .= '<span class="modifier-label">' . $this->trans('Limit') . '</span>';
        $html .= '<input type="number" class="group-modifier-limit" value="' . $this->escapeAttr($limit) . '" placeholder="–" min="1" step="1" title="' . $this->trans('Max items to return (empty = all)') . '">';
        $html .= '</span>';

        // Sort
        $html .= '<span class="modifier-inline modifier-sort">';
        $html .= '<span class="modifier-label">' . $this->trans('Sort') . '</span>';
        $html .= '<select class="group-modifier-sort">';
        foreach ($sortOptions as $value => $label) {
            $selected = ($value === $sortBy) ? ' selected' : '';
            $html .= '<option value="' . $this->escapeAttr($value) . '"' . $selected . '>' . $this->escapeAttr($label) . '</option>';
        }
        $html .= '</select>';
        $sortDirIcon = ($sortDir === 'ASC') ? 'arrow_upward' : 'arrow_downward';
        $html .= '<button type="button" class="btn-sort-dir" data-dir="' . $this->escapeAttr($sortDir) . '" title="' . $this->trans('Sort direction') . '">';
        $html .= $this->renderIcon($sortDirIcon);
        $html .= '</button>';
        $html .= '</span>';

        // Preview badge
        $html .= '<span class="group-preview-badge clickable" title="' . $this->trans('Preview results') . '">';
        $html .= $this->renderIcon('visibility') . ' <span class="preview-count"></span>';
        $html .= '</span>';

        $html .= '</div>';

        return $html;
    }

    /**
     * Render method dropdown options
     *
     * @param array $methods Available methods
     * @param string $selectedMethod Currently selected
     * @param bool $excludeAll Whether to exclude "All" option
     * @return string HTML options
     */
    public function renderMethodOptions($methods, $selectedMethod, $excludeAll = false)
    {
        $groupLabels = $this->getMethodGroupLabels();
        $html = '';

        // Group methods
        $grouped = [];
        $ungrouped = [];

        foreach ($methods as $methodKey => $methodDef) {
            if ($excludeAll && $methodKey === 'all') {
                continue;
            }

            $group = $methodDef['group'] ?? '';
            if ($group) {
                if (!isset($grouped[$group])) {
                    $grouped[$group] = [];
                }
                $grouped[$group][$methodKey] = $methodDef;
            } else {
                $ungrouped[$methodKey] = $methodDef;
            }
        }

        // Render ungrouped first
        foreach ($ungrouped as $methodKey => $methodDef) {
            $selected = ($selectedMethod === $methodKey) ? ' selected' : '';
            $html .= $this->renderMethodOption($methodKey, $methodDef, $selected);
        }

        // Render grouped with optgroups
        foreach ($grouped as $groupKey => $groupMethods) {
            $groupLabel = $groupLabels[$groupKey] ?? ucfirst(str_replace('_', ' ', $groupKey));
            $html .= '<optgroup label="' . $this->escapeAttr($groupLabel) . '">';

            foreach ($groupMethods as $methodKey => $methodDef) {
                $selected = ($selectedMethod === $methodKey) ? ' selected' : '';
                $html .= $this->renderMethodOption($methodKey, $methodDef, $selected);
            }

            $html .= '</optgroup>';
        }

        return $html;
    }

    /**
     * Render a single method option
     *
     * @param string $methodKey Method key
     * @param array $methodDef Method definition
     * @param string $selected Selected attribute
     * @return string HTML option
     */
    public function renderMethodOption($methodKey, $methodDef, $selected = '')
    {
        $html = '<option value="' . $this->escapeAttr($methodKey) . '"';
        $html .= ' data-value-type="' . $this->escapeAttr($methodDef['value_type'] ?? 'none') . '"';

        if (!empty($methodDef['icon'])) {
            $html .= ' data-icon="' . $this->escapeAttr($methodDef['icon']) . '"';
        }
        if (!empty($methodDef['search_entity'])) {
            $html .= ' data-search-entity="' . $this->escapeAttr($methodDef['search_entity']) . '"';
        }
        if (!empty($methodDef['options'])) {
            $html .= ' data-options="' . $this->escapeAttr(json_encode($methodDef['options'])) . '"';
        }

        $html .= $selected . '>' . $this->escapeAttr($methodDef['label'] ?? $methodKey) . '</option>';

        return $html;
    }

    /**
     * Render an exclude row
     *
     * @param string $blockType Block type
     * @param array $methods Available methods
     * @param array $exclude Exclude data
     * @param int $excludeIndex Exclude index
     * @return string HTML
     */
    public function renderExcludeRow($blockType, $methods, $exclude, $excludeIndex)
    {
        $excludeMethod = $exclude['method'] ?? 'specific';
        $excludeValues = $exclude['values'] ?? [];

        $html = '<div class="exclude-row" data-exclude-index="' . $excludeIndex . '">';

        // Header row
        $html .= '<div class="exclude-header-row">';
        $html .= '<div class="method-selector-wrapper">';

        $methodHelp = $this->getMethodHelpTooltip($excludeMethod, $blockType);
        $html .= '<span class="method-info-placeholder">';
        if (!empty($methodHelp)) {
            $html .= '<span class="mpr-info-wrapper" data-details="' . $this->escapeAttr($methodHelp) . '">';
            $html .= $this->renderIcon('info');
            $html .= '</span>';
        }
        $html .= '</span>';

        $html .= '<select class="exclude-method-select">';
        $html .= $this->renderMethodOptions($methods, $excludeMethod, true);
        $html .= '</select>';

        $html .= '<span class="condition-match-count no-matches">' . $this->renderIcon('visibility') . ' <span class="preview-count">0</span></span>';
        $html .= '</div>'; // End method-selector-wrapper

        $html .= '<button type="button" class="btn-remove-exclude-row" title="' . $this->trans('Remove this exception') . '">';
        $html .= $this->renderIcon('delete');
        $html .= '</button>';
        $html .= '</div>';

        // Value picker
        $html .= $this->renderValuePicker('exclude', $methods, $excludeMethod, $excludeValues);

        $html .= '</div>'; // End exclude-row

        return $html;
    }

    /**
     * Render value picker area
     *
     * @param string $section Section type (include/exclude)
     * @param array $methods Available methods
     * @param string $selectedMethod Selected method
     * @param mixed $values Current values
     * @return string HTML
     */
    public function renderValuePicker($section, $methods, $selectedMethod, $values)
    {
        $methodDef = $methods[$selectedMethod] ?? [];
        $valueType = $methodDef['value_type'] ?? 'none';
        $searchEntity = $methodDef['search_entity'] ?? '';
        $pickerClass = $section . '-picker';
        $chipsClass = $section . '-chips';
        $dataClass = $section . '-values-data';

        // Don't show picker for 'none' type
        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':
                $noItemsPlaceholder = $this->trans('No items selected - use search below');
                // Don't pre-wrap chips - JS will create the wrapper with toolbar when chips are added
                $html .= '<div class="entity-chips ' . $chipsClass . '" data-placeholder="' . $this->escapeAttr($noItemsPlaceholder) . '"></div>';
                $html .= '<div class="entity-search-box">';
                $html .= $this->renderIcon('search', 'entity-search-icon');
                $html .= '<input type="text" class="entity-search-input" placeholder="' . $this->trans('Search by name, reference, ID...') . '" autocomplete="off">';
                $html .= '<span class="search-loading" style="display:none;">' . $this->renderIcon('sync', 'es-spin') . '</span>';
                $html .= '</div>';
                $html .= '<input type="hidden" class="' . $dataClass . '" value="' . $this->escapeAttr(json_encode($values)) . '">';
                break;

            case 'pattern':
                $html .= '<div class="entity-chips pattern-chips" data-placeholder="' . $this->escapeAttr($this->trans('No patterns - press Enter to add')) . '"></div>';
                $html .= '<div class="pattern-input-row">';
                $html .= '<div class="pattern-tag draft-tag" data-case-sensitive="0">';
                $html .= '<button type="button" class="btn-toggle-case" title="' . $this->escapeAttr($this->trans('Case insensitive - click to toggle')) . '"><span class="case-icon">aa</span></button>';
                $html .= '<input type="text" class="pattern-input" value="" placeholder="' . $this->escapeAttr($this->trans('e.g. *cotton*')) . '">';
                $html .= '<span class="pattern-match-count"></span>';
                $html .= '<button type="button" class="btn-add-pattern" title="' . $this->escapeAttr($this->trans('Add pattern (Enter)')) . '">' . $this->renderIcon('add') . '</button>';
                $html .= '</div>';
                $html .= '</div>';
                $html .= '<input type="hidden" class="' . $dataClass . '" value="' . $this->escapeAttr(json_encode($values)) . '">';
                break;

            case 'numeric_range':
                $minValue = '';
                $maxValue = '';
                if (is_array($values)) {
                    $minValue = $values['min'] ?? ($values[0] ?? '');
                    $maxValue = $values['max'] ?? ($values[1] ?? '');
                }
                $html .= '<div class="numeric-range-box">';
                $html .= '<input type="number" class="range-min-input" value="' . $this->escapeAttr($minValue) . '" placeholder="' . $this->trans('Min') . '" step="0.01">';
                $html .= '<span class="range-separator">-</span>';
                $html .= '<input type="number" class="range-max-input" value="' . $this->escapeAttr($maxValue) . '" placeholder="' . $this->trans('Max') . '" step="0.01">';
                $html .= '</div>';
                $html .= '<input type="hidden" class="' . $dataClass . '" value="' . $this->escapeAttr(json_encode($values)) . '">';
                break;

            case 'multi_numeric_range':
                $ranges = is_array($values) ? $values : [];
                $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->trans('Min') . '" step="0.01">';
                $html .= '<span class="range-separator">-</span>';
                $html .= '<input type="number" class="range-max-input" value="" placeholder="' . $this->trans('Max') . '" step="0.01">';
                $html .= '<button type="button" class="btn-add-range" title="' . $this->trans('Add range') . '">' . $this->renderIcon('add') . '</button>';
                $html .= '</div>';
                $html .= '</div>';
                $html .= '<input type="hidden" class="' . $dataClass . '" value="' . $this->escapeAttr(json_encode($ranges)) . '">';
                break;

            case 'multi_select_tiles':
                $options = $methodDef['options'] ?? [];
                $selectedValues = is_array($values) ? $values : [];
                $html .= '<div class="multi-select-tiles">';
                foreach ($options as $optKey => $optData) {
                    $isSelected = in_array($optKey, $selectedValues);
                    $selectedClass = $isSelected ? ' selected' : '';
                    $optLabel = is_array($optData) ? ($optData['label'] ?? $optKey) : $optData;
                    $optIcon = is_array($optData) ? ($optData['icon'] ?? '') : '';
                    $html .= '<button type="button" class="tile-option' . $selectedClass . '" data-value="' . $this->escapeAttr($optKey) . '">';
                    if ($optIcon) {
                        $html .= $this->renderIcon($optIcon) . ' ';
                    }
                    $html .= '<span class="tile-label">' . $this->escapeAttr($optLabel) . '</span>';
                    $html .= '</button>';
                }
                $html .= '</div>';
                $html .= '<input type="hidden" class="' . $dataClass . '" value="' . $this->escapeAttr(json_encode($selectedValues)) . '">';
                break;

            case 'date_range':
                $fromDate = '';
                $toDate = '';
                if (is_array($values)) {
                    $fromDate = $values['from'] ?? ($values[0] ?? '');
                    $toDate = $values['to'] ?? ($values[1] ?? '');
                }
                $html .= '<div class="date-range-box">';
                $html .= '<input type="date" class="date-from-input" value="' . $this->escapeAttr($fromDate) . '" placeholder="' . $this->trans('From') . '">';
                $html .= '<span class="range-separator">-</span>';
                $html .= '<input type="date" class="date-to-input" value="' . $this->escapeAttr($toDate) . '" placeholder="' . $this->trans('To') . '">';
                $html .= '</div>';
                $html .= '<input type="hidden" class="' . $dataClass . '" value="' . $this->escapeAttr(json_encode($values)) . '">';
                break;

            case 'select':
                $options = $methodDef['options'] ?? [];
                $selectedValue = is_array($values) && !empty($values[0]) ? $values[0] : (is_string($values) ? $values : '');
                $html .= '<div class="select-input-box">';
                $html .= '<select class="select-value-input">';
                foreach ($options as $optKey => $optLabel) {
                    $selected = ($selectedValue === $optKey) ? ' selected' : '';
                    $html .= '<option value="' . $this->escapeAttr($optKey) . '"' . $selected . '>' . $this->escapeAttr($optLabel) . '</option>';
                }
                $html .= '</select>';
                $html .= '</div>';
                $html .= '<input type="hidden" class="' . $dataClass . '" value="' . $this->escapeAttr(json_encode($values)) . '">';
                break;

            case 'boolean':
                $html .= '<div class="boolean-input-box">';
                $html .= '<span class="boolean-label">' . $this->trans('Yes') . '</span>';
                $html .= '</div>';
                $html .= '<input type="hidden" class="' . $dataClass . '" value="' . $this->escapeAttr(json_encode([true])) . '">';
                break;

            default:
                $html .= '<input type="hidden" class="' . $dataClass . '" value="[]">';
                break;
        }

        $html .= '</div>';

        return $html;
    }

    /**
     * Check if a condition has valid values for its method type
     *
     * @param string $method Method key
     * @param mixed $values Values
     * @param array $methods Method definitions
     * @return bool
     */
    public function isConditionValid($method, $values, $methods)
    {
        if ($method === 'all') {
            return true;
        }

        $methodDef = $methods[$method] ?? [];
        $valueType = $methodDef['value_type'] ?? 'entity_search';

        if ($valueType === 'boolean') {
            return true;
        }

        if ($valueType === 'multi_select_tiles') {
            return is_array($values) && !empty($values);
        }

        if ($valueType === 'multi_numeric_range') {
            if (!is_array($values) || empty($values)) {
                return false;
            }
            foreach ($values as $range) {
                if (is_array($range) && (
                    (isset($range['min']) && $range['min'] !== null && $range['min'] !== '') ||
                    (isset($range['max']) && $range['max'] !== null && $range['max'] !== '')
                )) {
                    return true;
                }
            }
            return false;
        }

        if (is_array($values)) {
            if (empty($values)) {
                return false;
            }
            if (isset($values['min']) || isset($values['max']) || isset($values['from']) || isset($values['to'])) {
                return ($values['min'] ?? null) !== null
                    || ($values['max'] ?? null) !== null
                    || !empty($values['from'])
                    || !empty($values['to']);
            }
            return true;
        }

        return !empty($values);
    }

    /**
     * Get method group labels
     *
     * @return array
     */
    protected function getMethodGroupLabels()
    {
        return [
            'select_by' => $this->trans('Select by'),
            'filter_by' => $this->trans('Filter by'),
            'advanced' => $this->trans('Advanced'),
        ];
    }

    /**
     * Get method help tooltip content
     *
     * @param string $method Method key
     * @param string $blockType Block type
     * @return string HTML
     */
    protected function getMethodHelpTooltip($method, $blockType)
    {
        $helpContent = $this->getAllMethodHelpContent();
        $key = $blockType . '.' . $method;

        if (isset($helpContent[$key])) {
            return $helpContent[$key];
        }

        // Try generic method help
        if (isset($helpContent[$method])) {
            return $helpContent[$method];
        }

        return '';
    }

    /**
     * Get all method help content
     *
     * @return array
     */
    public function getAllMethodHelpContent()
    {
        return [
            // Generic method help
            'all' => '<strong>' . $this->trans('All Items') . '</strong><p>' . $this->trans('Selects all items of this type. Use with exclusions to target most items except specific ones.') . '</p>',
            'specific' => '<strong>' . $this->trans('Specific Items') . '</strong><p>' . $this->trans('Search and select individual items by name, ID, or reference.') . '</p>',
            'by_name_pattern' => '<strong>' . $this->trans('Name Pattern') . '</strong><p>' . $this->trans('Match items by name using wildcards. Use * for multiple characters, ? for single character.') . '</p><p>' . $this->trans('Examples: *cotton*, shirt-*, ?-pack') . '</p>',
            'by_reference_pattern' => '<strong>' . $this->trans('Reference Pattern') . '</strong><p>' . $this->trans('Match items by SKU/reference using wildcards.') . '</p>',
            'by_price_range' => '<strong>' . $this->trans('Price Range') . '</strong><p>' . $this->trans('Select items within a price range. Leave min or max empty for open-ended ranges.') . '</p>',
            'by_stock_status' => '<strong>' . $this->trans('Stock Status') . '</strong><p>' . $this->trans('Filter by availability: in stock, out of stock, or low stock.') . '</p>',
            'by_date_added' => '<strong>' . $this->trans('Date Added') . '</strong><p>' . $this->trans('Select items added within a date range.') . '</p>',
            'by_active_status' => '<strong>' . $this->trans('Active Status') . '</strong><p>' . $this->trans('Filter by active/inactive status.') . '</p>',

            // Products specific
            'products.by_category' => '<strong>' . $this->trans('By Category') . '</strong><p>' . $this->trans('Select all products from specific categories. Includes subcategories by default.') . '</p>',
            'products.by_manufacturer' => '<strong>' . $this->trans('By Manufacturer') . '</strong><p>' . $this->trans('Select all products from specific manufacturers/brands.') . '</p>',
            'products.by_supplier' => '<strong>' . $this->trans('By Supplier') . '</strong><p>' . $this->trans('Select all products from specific suppliers.') . '</p>',
            'products.by_attribute' => '<strong>' . $this->trans('By Attribute') . '</strong><p>' . $this->trans('Select products with specific attributes (size, color, etc.).') . '</p>',
            'products.by_feature' => '<strong>' . $this->trans('By Feature') . '</strong><p>' . $this->trans('Select products with specific features (material, brand, etc.).') . '</p>',
            'products.by_tag' => '<strong>' . $this->trans('By Tag') . '</strong><p>' . $this->trans('Select products with specific tags.') . '</p>',
            'products.by_condition' => '<strong>' . $this->trans('By Condition') . '</strong><p>' . $this->trans('Filter by product condition: new, used, or refurbished.') . '</p>',
            'products.by_visibility' => '<strong>' . $this->trans('By Visibility') . '</strong><p>' . $this->trans('Filter by visibility settings: catalog, search, both, or nowhere.') . '</p>',
            'products.by_weight_range' => '<strong>' . $this->trans('By Weight') . '</strong><p>' . $this->trans('Select products within a weight range.') . '</p>',
            'products.on_sale' => '<strong>' . $this->trans('On Sale') . '</strong><p>' . $this->trans('Select products currently marked as on sale or with specific price rules.') . '</p>',
            'products.new_products' => '<strong>' . $this->trans('New Products') . '</strong><p>' . $this->trans('Select products marked as new (within the "new product" period).') . '</p>',
            'products.best_sellers' => '<strong>' . $this->trans('Best Sellers') . '</strong><p>' . $this->trans('Select top-selling products based on sales volume.') . '</p>',

            // Categories specific
            'categories.by_parent' => '<strong>' . $this->trans('By Parent Category') . '</strong><p>' . $this->trans('Select categories that are children of specific parent categories.') . '</p>',
            'categories.by_depth' => '<strong>' . $this->trans('By Depth Level') . '</strong><p>' . $this->trans('Select categories at a specific depth in the hierarchy (0 = root, 1 = top level, etc.).') . '</p>',
            'categories.has_products' => '<strong>' . $this->trans('Has Products') . '</strong><p>' . $this->trans('Select only categories that contain products (directly or in subcategories).') . '</p>',
        ];
    }

    /**
     * Get JS configuration for the widget
     *
     * @param array $config Widget configuration
     * @param array $enabledBlocks Enabled blocks
     * @return array
     */
    protected function getJsConfig($config, $enabledBlocks)
    {
        $blockSettings = $config['blocks'] ?? [];

        // Add mode to each block
        foreach ($enabledBlocks as $blockType => &$blockDef) {
            $blockMode = $blockSettings[$blockType]['mode'] ?? 'multi';
            $blockDef['mode'] = $blockMode;
        }

        // Build AJAX URL
        $ajaxUrl = '';
        if ($this->controllerName && isset($this->context->link)) {
            $ajaxUrl = $this->context->link->getAdminLink($this->controllerName);
        }

        return [
            'id' => $config['id'],
            'name' => $config['name'],
            'namePrefix' => $config['name_prefix'],
            'mode' => $config['mode'] ?? 'multi',
            'blocks' => $enabledBlocks,
            'ajaxUrl' => $ajaxUrl,
            'skipInitialCounts' => !empty($config['skip_initial_counts']) || !empty($config['show_empty_groups']),
            'trans' => [
                'all' => $this->trans('All'),
                'include' => $this->trans('Include'),
                'exclude' => $this->trans('Except'),
                'except' => $this->trans('EXCEPT'),
                'add_group' => $this->trans('Add selection group'),
                'add_exceptions' => $this->trans('Add exceptions'),
                'add_another_exception' => $this->trans('Add another exception'),
                'remove_group' => $this->trans('Remove group'),
                'remove_this_exception' => $this->trans('Remove this exception'),
                'search_placeholder' => $this->trans('Search by name, reference, ID...'),
                'no_results' => $this->trans('No results found'),
                'loading' => $this->trans('Loading...'),
                'load_more' => $this->trans('Load more'),
                'cancel' => $this->trans('Cancel'),
                'done' => $this->trans('Done'),
                'select_all' => $this->trans('Select all'),
                'clear' => $this->trans('Clear'),
                'no_groups' => $this->trans('No selection groups - all items included'),
                'group' => $this->trans('Group'),
                'items_matched' => $this->trans('matched'),
                'more' => $this->trans('more'),
                'no_preview' => $this->trans('No items to preview'),
                'no_items_selected' => $this->trans('No items selected - use search below'),
                'filter_results' => $this->trans('Filter results...'),
                'load' => $this->trans('Load'),
                'of' => $this->trans('of'),
                'remaining' => $this->trans('remaining'),
                'by_entity' => $this->trans('By Entity'),
                'by_property' => $this->trans('By Property'),
                'by_text' => $this->trans('By Text'),
                'by_range' => $this->trans('By Range'),
                'enter_pattern' => $this->trans('Enter text pattern...'),
                'min' => $this->trans('Min'),
                'max' => $this->trans('Max'),
                'yes' => $this->trans('Yes'),
                'comb_mode_products' => $this->trans('Products with these combinations'),
                'comb_mode_combinations' => $this->trans('Only these exact combinations'),
                'limit_and_sort' => $this->trans('Limit & Sort'),
                'limit_to_top' => $this->trans('Limit to top'),
                'sort_by' => $this->trans('Sort by'),
                'default_order' => $this->trans('Default order'),
                'items' => $this->trans('items'),
                'desc' => $this->trans('Desc'),
                'asc' => $this->trans('Asc'),
                'top' => $this->trans('Top'),
                'included' => $this->trans('included'),
                'nothing_selected' => $this->trans('Nothing selected'),
                'no_item_selected' => $this->trans('No item selected'),
            ],
            'methodHelp' => $this->getAllMethodHelpContent(),
            'combinationMode' => $config['combination_mode'] ?? 'products',
            'emptyMeansAll' => $config['empty_means_all'] ?? true,
        ];
    }

    /**
     * Get status text for a target block
     *
     * @param array $groups Groups
     * @param array $blockDef Block definition
     * @param string $blockType Block type
     * @return string
     */
    public function getTargetBlockStatusText($groups, $blockDef, $blockType)
    {
        if (empty($groups)) {
            return $this->trans('All') . ' ' . ($blockDef['entity_label_plural'] ?? 'items');
        }

        $groupCount = count($groups);
        return $groupCount . ' ' . ($groupCount === 1 ? $this->trans('group') : $this->trans('groups'));
    }

    /**
     * Get sort icon class
     *
     * @param string $sortBy Sort field
     * @param string $sortDir Sort direction
     * @return string
     */
    protected function getSortIcon($sortBy, $sortDir = 'DESC')
    {
        $isAsc = ($sortDir === 'ASC');

        switch ($sortBy) {
            case 'name':
                return $isAsc ? 'sort_by_alpha' : 'sort_by_alpha';
            case 'price':
            case 'quantity':
            case 'product_count':
                return $isAsc ? 'sort' : 'sort';
            case 'date_add':
            case 'newest_products':
                return $isAsc ? 'sort' : 'sort';
            case 'sales':
            case 'total_sales':
                return $isAsc ? 'arrow_upward' : 'arrow_downward';
            case 'position':
                return $isAsc ? 'sort' : 'sort';
            case 'random':
                return 'shuffle';
            default:
                return $isAsc ? 'arrow_upward' : 'arrow_downward';
        }
    }
}
