<?php

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

class AIEO_DMM_Category_Extras extends AIEO_DMM_Module_Base {

    public function get_id()       { return 'category_extras'; }
    public function get_label()    { return __('Category Extras', 'ai-eshop-optimizer'); }
    public function get_priority() { return 75; }
    public function get_group()    { return 'visuals'; }

    
    const LEGACY_PREFIX = 'woocommerce_advanced_categories_';

    
    public static function templates() {
        return (array) apply_filters('aieo_dmm_category_extras_templates', array(
            'default'        => __('Default (theme archive)', 'ai-eshop-optimizer'),
            'hero'           => __('Hero (image + headline + description)', 'ai-eshop-optimizer'),
            'hero_brands'    => __('Hero + featured brands grid', 'ai-eshop-optimizer'),
            'landing'        => __('Landing page (no products grid)', 'ai-eshop-optimizer'),
            'hidden'         => __('Hidden (404 the archive)', 'ai-eshop-optimizer'),
        ));
    }

    public function register_hooks() {
        add_action('wp_ajax_aieo_dmm_category_extras_migrate',       array($this, 'ajax_migrate'));
        add_action('wp_ajax_aieo_dmm_category_extras_h3_migrate',    array($this, 'ajax_migrate_h3_headlines'));
        add_action('wp_ajax_aieo_dmm_category_extras_save',          array($this, 'ajax_save'));
        add_action('wp_ajax_aieo_dmm_category_extras_search',        array($this, 'ajax_search'));
        add_action('wp_ajax_aieo_dmm_marketplace_tax_browse',         array($this, 'ajax_marketplace_tax_browse'));
        add_action('wp_ajax_aieo_dmm_marketplace_tax_search',         array($this, 'ajax_marketplace_tax_search'));
        add_action('wp_ajax_aieo_dmm_category_extras_save_settings', array($this, 'ajax_save_settings'));

        
        
        add_shortcode('aieo_category_hero',  array($this, 'shortcode_category_hero'));
        add_shortcode('aieo_category_chips', array($this, 'shortcode_category_chips'));
        add_action('init',               array($this, 'register_block'));
        add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets'));

        
        
        add_action('pre_get_posts', array($this, 'exclude_blog_hero_post'));

        
        
        
        
        if (!is_admin()) {
            return;
        }
        foreach ($this->managed_taxonomies() as $tax) {
            
            
            
            add_action($tax . '_edit_form_fields', array($this, 'render_term_extras'), 1, 2);
            add_action('edited_'  . $tax,           array($this, 'save_term_extras'),   10, 2);
            add_action('created_' . $tax,           array($this, 'save_term_extras'),   10, 2);
        }
        add_action('admin_enqueue_scripts', array($this, 'enqueue_term_admin_assets'));
    }

    
    private function managed_taxonomies() {
        return (array) apply_filters(
            'aieo_dmm_category_extras_managed_taxonomies',
            array('product_cat')
        );
    }

    
    private function description_editor_taxonomies() {
        
        
        
        
        
        
        
        $list = array_merge(
            $this->managed_taxonomies(),
            array(
                'product_brand', 'product_tag', 'supplier',
                'pa_need', 'pa-need', 'category', 'post_tag',
            )
        );
        $list = (array) apply_filters('aieo_dmm_description_editor_taxonomies', $list);
        return array_values(array_unique(array_filter(array_map('sanitize_key', $list))));
    }

    public function is_enabled() {
        if (method_exists('AIEO_DMM_Settings', 'is_module_enabled')) {
            return (bool) AIEO_DMM_Settings::is_module_enabled('category_extras');
        }
        return true;
    }

    
    
    

    
    public static function table() {
        global $wpdb;
        return $wpdb->prefix . 'aieo_dmm_category_extras';
    }

    
    public static function get($taxonomy, $term_id) {
        global $wpdb;
        $taxonomy = sanitize_key($taxonomy);
        $term_id  = (int) $term_id;
        if (!$taxonomy || !$term_id) return null;

        $tbl = self::table();
        
        $row = $wpdb->get_row(
            $wpdb->prepare("SELECT * FROM {$tbl} WHERE taxonomy = %s AND term_id = %d LIMIT 1", $taxonomy, $term_id),
            ARRAY_A
        );
        if (!$row) return null;
        $row['landing_pages'] = self::decode_landing_pages($row['landing_pages']);
        foreach (array('related_brand_ids','related_post_ids','related_campaign_ids') as $k) {
            $row[$k] = isset($row[$k]) ? self::decode_id_list($row[$k]) : array();
        }
        return $row;
    }

    
    public static function save($taxonomy, $term_id, array $data) {
        global $wpdb;
        $taxonomy = sanitize_key($taxonomy);
        $term_id  = (int) $term_id;
        if (!$taxonomy || !$term_id) return false;

        $tbl   = self::table();
        $patch = self::sanitize_patch($data);
        $patch['taxonomy'] = $taxonomy;
        $patch['term_id']  = $term_id;

        $existing = $wpdb->get_var($wpdb->prepare(
            "SELECT id FROM {$tbl} WHERE taxonomy = %s AND term_id = %d LIMIT 1",
            $taxonomy, $term_id
        ));
        if ($existing) {
            return $wpdb->update($tbl, $patch, array('id' => (int) $existing));
        }
        return $wpdb->insert($tbl, $patch);
    }

    
    private static function sanitize_patch(array $data) {
        $out = array();
        if (array_key_exists('headline', $data)) {
            $out['headline'] = wp_kses_post((string) $data['headline']);
        }
        if (array_key_exists('description', $data)) {
            $out['description'] = wp_kses_post((string) $data['description']);
        }
        if (array_key_exists('image_mobile_id', $data)) {
            $out['image_mobile_id'] = $data['image_mobile_id'] ? (int) $data['image_mobile_id'] : null;
        }
        if (array_key_exists('image_desktop_id', $data)) {
            $out['image_desktop_id'] = $data['image_desktop_id'] ? (int) $data['image_desktop_id'] : null;
        }
        if (array_key_exists('seo_title', $data)) {
            $out['seo_title'] = sanitize_text_field((string) $data['seo_title']);
        }
        if (array_key_exists('sidebar_text', $data)) {
            $out['sidebar_text'] = wp_kses_post((string) $data['sidebar_text']);
        }
        if (array_key_exists('footer_text', $data)) {
            $out['footer_text'] = wp_kses_post((string) $data['footer_text']);
        }
        if (array_key_exists('columns', $data)) {
            $c = (int) $data['columns'];
            $out['columns'] = ($c >= 1 && $c <= 6) ? $c : null;
        }
        if (array_key_exists('disable_nested', $data)) {
            $out['disable_nested'] = !empty($data['disable_nested']) ? 1 : 0;
        }
        if (array_key_exists('template', $data)) {
            $tpl = sanitize_key((string) $data['template']);
            $allowed = array_keys(self::templates());
            $out['template'] = in_array($tpl, $allowed, true) ? $tpl : 'default';
        }
        if (array_key_exists('landing_pages', $data)) {
            $out['landing_pages'] = self::encode_landing_pages($data['landing_pages']);
        }
        if (array_key_exists('pro_only', $data)) {
            $out['pro_only'] = !empty($data['pro_only']) ? 1 : 0;
        }
        if (array_key_exists('reference_quantity', $data)) {
            $rq = $data['reference_quantity'];
            $out['reference_quantity'] = ($rq === null || $rq === '') ? null : max(0, (int) $rq);
        }
        if (array_key_exists('synonyms', $data)) {
            $out['synonyms'] = (string) $data['synonyms'] !== '' ? (string) $data['synonyms'] : null;
        }
        foreach (array('season_start', 'season_end', 'season_midpoint') as $key) {
            if (!array_key_exists($key, $data)) continue;
            $val = (string) $data[$key];
            
            $out[$key] = (preg_match('/^\d{4}-\d{2}-\d{2}$/', $val) && $val !== '0000-00-00') ? $val : null;
        }
        
        
        
        foreach (array('google_category_id', 'facebook_category_id') as $idkey) {
            if (array_key_exists($idkey, $data)) {
                $v = $data[$idkey];
                $out[$idkey] = ($v === null || $v === '') ? null : max(0, (int) $v);
                if ($out[$idkey] === 0) { $out[$idkey] = null; }
            }
        }
        foreach (array('google_category_path', 'facebook_category_path') as $pathkey) {
            if (array_key_exists($pathkey, $data)) {
                $v = sanitize_text_field((string) $data[$pathkey]);
                $out[$pathkey] = $v !== '' ? mb_substr($v, 0, 500) : null;
            }
        }
        foreach (array('related_brand_ids', 'related_post_ids', 'related_campaign_ids') as $key) {
            if (array_key_exists($key, $data)) {
                $out[$key] = self::encode_id_list($data[$key]);
            }
        }
        return $out;
    }

    
    private static function encode_id_list($val) {
        if (!is_array($val)) return null;
        $clean = array_values(array_unique(array_map('intval', $val)));
        $clean = array_values(array_filter($clean, static function ($v) { return $v > 0; }));
        return empty($clean) ? null : wp_json_encode($clean);
    }

    
    private static function decode_id_list($json) {
        if (!$json) return array();
        $arr = json_decode((string) $json, true);
        return is_array($arr) ? array_values(array_map('intval', $arr)) : array();
    }

    
    private static function encode_landing_pages($val) {
        if (is_array($val)) {
            $clean = array();
            foreach ($val as $lp) {
                if (!is_array($lp)) continue;
                $clean[] = array(
                    'title'    => isset($lp['title']) ? sanitize_text_field((string) $lp['title']) : '',
                    'link'     => isset($lp['link'])  ? esc_url_raw((string) $lp['link'])         : '',
                    'image_id' => isset($lp['image_id']) ? (int) $lp['image_id'] : 0,
                );
            }
            return wp_json_encode(array_values($clean));
        }
        return null;
    }

    
    private static function decode_landing_pages($json) {
        if (!$json) return array();
        $arr = json_decode((string) $json, true);
        return is_array($arr) ? $arr : array();
    }

    
    
    

    
    public function run_migration() {
        $taxonomies = (array) apply_filters(
            'aieo_dmm_category_extras_migration_taxonomies',
            array('product_cat', 'product_brand')
        );

        $stats = array(
            'processed'    => 0,
            'migrated'     => 0,
            'skipped'      => 0,
            'taxonomies'   => array(),
            'pods_loaded'  => array(), 
        );

        foreach ($taxonomies as $taxonomy) {
            if (!taxonomy_exists($taxonomy)) continue;
            $stats['taxonomies'][$taxonomy] = 0;

            
            
            
            $pods_cache = $this->load_pods_caches($taxonomy);
            $stats['pods_loaded'][$taxonomy] = !empty($pods_cache['pod_id']);

            $term_ids = get_terms(array(
                'taxonomy'   => $taxonomy,
                'hide_empty' => false,
                'fields'     => 'ids',
            ));
            if (is_wp_error($term_ids) || empty($term_ids)) continue;

            foreach ($term_ids as $term_id) {
                $stats['processed']++;
                $patch = $this->extract_legacy_for_term((int) $term_id);
                if (!empty($pods_cache['pod_id'])) {
                    $patch = array_merge($patch, $this->extract_pods_for_term((int) $term_id, $pods_cache));
                }
                if (empty($patch)) {
                    $stats['skipped']++;
                    continue;
                }
                self::save($taxonomy, (int) $term_id, $patch);
                $stats['migrated']++;
                $stats['taxonomies'][$taxonomy]++;
            }
        }
        update_option('aieo_dmm_category_extras_migrated_at', time(), false);
        return $stats;
    }

    
    
    

    
    private function load_pods_caches($pod_name) {
        global $wpdb;

        $cache = array(
            'pod_id'     => 0,
            'pods_table' => array(),
            'rel_fields' => array(),
            'rel_data'   => array(),
        );

        $pod_id = (int) $wpdb->get_var($wpdb->prepare(
            "SELECT ID FROM {$wpdb->posts}
             WHERE post_type = '_pods_pod' AND post_name = %s LIMIT 1",
            $pod_name
        ));
        if (!$pod_id) return $cache;
        $cache['pod_id'] = $pod_id;

        
        
        $relevant = array('related_brand', 'related_posts', 'related_campaigns');
        
        $field_rows = $wpdb->get_results($wpdb->prepare(
            "SELECT ID, post_name FROM {$wpdb->posts}
             WHERE post_type = '_pods_field' AND post_parent = %d
             AND post_name IN ('" . implode("','", $relevant) . "')",
            $pod_id
        ));
        $field_id_to_name = array();
        foreach ((array) $field_rows as $row) {
            $cache['rel_fields'][$row->post_name]   = (int) $row->ID;
            $field_id_to_name[(int) $row->ID]       = $row->post_name;
        }

        
        $tbl = $wpdb->prefix . 'pods_' . $pod_name;
        
        $exists = ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tbl)) === $tbl);
        if ($exists) {
            
            $rows = $wpdb->get_results("SELECT * FROM {$tbl}", ARRAY_A);
            foreach ((array) $rows as $row) {
                if (!isset($row['id'])) continue;
                $cache['pods_table'][(int) $row['id']] = $row;
            }
        }

        
        
        if (!empty($field_id_to_name)) {
            $field_ids   = array_keys($field_id_to_name);
            $place       = implode(',', array_fill(0, count($field_ids), '%d'));
            $params      = array_merge(array($pod_id), $field_ids);
            
            $rows = $wpdb->get_results($wpdb->prepare(
                "SELECT item_id, field_id, related_item_id
                 FROM {$wpdb->prefix}podsrel
                 WHERE pod_id = %d AND field_id IN ({$place})
                 ORDER BY item_id, field_id, weight ASC",
                $params
            ));
            foreach ((array) $rows as $row) {
                $name = $field_id_to_name[(int) $row->field_id] ?? null;
                if (!$name) continue;
                $cache['rel_data'][(int) $row->item_id][$name][] = (int) $row->related_item_id;
            }
        }

        return $cache;
    }

    
    private function extract_pods_for_term($term_id, array $cache) {
        $patch = array();

        
        if (isset($cache['pods_table'][$term_id])) {
            $row = $cache['pods_table'][$term_id];
            if (!empty($row['pro_only'])) {
                $patch['pro_only'] = 1;
            }
            if (isset($row['reference_quantity']) && $row['reference_quantity'] !== null && $row['reference_quantity'] !== '') {
                $rq = (int) $row['reference_quantity'];
                if ($rq > 0) $patch['reference_quantity'] = $rq;
            }
            if (!empty($row['synonyms'])) {
                $patch['synonyms'] = (string) $row['synonyms'];
            }
            foreach (array('season_start', 'season_end', 'season_midpoint') as $col) {
                if (!empty($row[$col]) && $row[$col] !== '0000-00-00') {
                    $patch[$col] = (string) $row[$col];
                }
            }
        }

        
        if (isset($cache['rel_data'][$term_id])) {
            $rels = $cache['rel_data'][$term_id];
            if (!empty($rels['related_brand'])) {
                $patch['related_brand_ids'] = $rels['related_brand'];
            }
            if (!empty($rels['related_posts'])) {
                $patch['related_post_ids'] = $rels['related_posts'];
            }
            if (!empty($rels['related_campaigns'])) {
                $patch['related_campaign_ids'] = $rels['related_campaigns'];
            }
        }

        return $patch;
    }

    
    private function extract_legacy_for_term($term_id) {
        $patch = array();

        $seo = get_term_meta($term_id, self::LEGACY_PREFIX . 'seo_title', true);
        if ($seo !== '') $patch['seo_title'] = $seo;

        $bg = get_term_meta($term_id, self::LEGACY_PREFIX . 'background', true);
        
        if (is_array($bg) && !empty($bg['id'])) {
            $patch['image_desktop_id'] = (int) $bg['id'];
        } elseif (is_numeric($bg) && (int) $bg > 0) {
            $patch['image_desktop_id'] = (int) $bg;
        }

        $short = get_term_meta($term_id, self::LEGACY_PREFIX . 'short_description', true);
        if ($short !== '') $patch['headline'] = $short;

        $sidebar = get_term_meta($term_id, self::LEGACY_PREFIX . 'sidebar_text', true);
        if ($sidebar !== '') $patch['sidebar_text'] = $sidebar;

        $footer = get_term_meta($term_id, self::LEGACY_PREFIX . 'footer_text', true);
        if ($footer !== '') $patch['footer_text'] = $footer;

        $cols = get_term_meta($term_id, self::LEGACY_PREFIX . 'product_category_columns', true);
        if ($cols !== '' && (int) $cols > 0) $patch['columns'] = (int) $cols;

        $nested = get_term_meta($term_id, self::LEGACY_PREFIX . 'disable_nested_categories', true);
        if ($nested === '1' || $nested === 1) $patch['disable_nested'] = 1;

        
        
        $landing = array();
        for ($i = 1; $i <= 8; $i++) {
            $title = get_term_meta($term_id, self::LEGACY_PREFIX . 'landingpage_' . $i . '_title', true);
            $link  = get_term_meta($term_id, self::LEGACY_PREFIX . 'landingpage_' . $i . '_link',  true);
            $img   = get_term_meta($term_id, self::LEGACY_PREFIX . 'landingpage_' . $i . '_image', true);

            $img_id = 0;
            if (is_array($img) && !empty($img['id'])) {
                $img_id = (int) $img['id'];
            } elseif (is_numeric($img) && (int) $img > 0) {
                $img_id = (int) $img;
            }
            if ($title === '' && $link === '' && !$img_id) continue;
            $landing[] = array(
                'title'    => (string) $title,
                'link'     => (string) $link,
                'image_id' => $img_id,
            );
        }
        if (!empty($landing)) $patch['landing_pages'] = $landing;

        return $patch;
    }

    
    
    

    public function ajax_migrate() {
        AIEO_DMM::verify_ajax_request();
        
        
        if (function_exists('ob_get_level') && ob_get_level()) { @ob_end_clean(); }
        $stats = $this->run_migration();
        wp_send_json_success(array(
            'stats'        => $stats,
            'migrated_at'  => time(),
        ));
    }

    
    public function run_h3_headline_migration() {
        $stats = array(
            'processed'      => 0,
            'headlines'      => 0,
            'style_stripped' => 0,
            'skipped'        => 0,
            'errors'         => 0,
            'taxonomies'     => array(),
        );

        
        
        
        $nav_module = null;
        if (class_exists('AIEO_DMM') && method_exists('AIEO_DMM', 'instance')) {
            $dmm = AIEO_DMM::instance();
            if ($dmm && method_exists($dmm, 'get_module')) {
                $nav_module = $dmm->get_module('navigator');
            }
        }
        if ($nav_module) {
            remove_action('created_term', array($nav_module, 'on_term_change'), 99);
            remove_action('edited_term',  array($nav_module, 'on_term_change'), 99);
        }

        foreach ($this->managed_taxonomies() as $taxonomy) {
            if (!taxonomy_exists($taxonomy)) continue;
            $stats['taxonomies'][$taxonomy] = 0;

            $terms = get_terms(array(
                'taxonomy'   => $taxonomy,
                'hide_empty' => false,
            ));
            if (is_wp_error($terms) || empty($terms)) continue;

            foreach ($terms as $term) {
                $stats['processed']++;
                $desc = (string) $term->description;
                if ($desc === '') {
                    $stats['skipped']++;
                    continue;
                }

                $headline_inner = '';
                $headline_offset = 0;
                $headline_length = 0;
                $stripped        = '';

                
                if (preg_match('#^\s*<h3\b[^>]*>(.*?)</h3>\s*#is', $desc, $m, PREG_OFFSET_CAPTURE)) {
                    $headline_inner  = trim((string) $m[1][0]);
                    $headline_offset = (int) $m[0][1];
                    $headline_length = strlen((string) $m[0][0]);
                }
                
                
                
                elseif (preg_match('#^\s*<strong\b[^>]*>(.*?)</strong>\s*(?:<br\s*/?>|\r\n|\n|$)#is', $desc, $m, PREG_OFFSET_CAPTURE)) {
                    $headline_inner  = trim((string) $m[1][0]);
                    $headline_offset = (int) $m[0][1];
                    $headline_length = strlen((string) $m[0][0]);
                }
                
                
                elseif (preg_match('#^\s*<h2\b[^>]*>(.*?)</h2>\s*#is', $desc, $m, PREG_OFFSET_CAPTURE)) {
                    $headline_inner  = trim((string) $m[1][0]);
                    $headline_offset = (int) $m[0][1];
                    $headline_length = strlen((string) $m[0][0]);
                }
                
                

                $new_desc = $desc;
                if ($headline_length > 0) {
                    
                    $stripped = trim(html_entity_decode(wp_strip_all_tags($headline_inner), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
                    if ($stripped === '') {
                        $headline_inner  = '';
                        $headline_length = 0;
                    } else {
                        $new_desc = (string) substr_replace($desc, '', $headline_offset, $headline_length);
                    }
                }

                
                
                
                $cleaned = $new_desc;
                $cleaned = (string) preg_replace('#\s*text-align\s*:\s*[^;"\']+;?\s*#i', '', $cleaned);
                $cleaned = (string) preg_replace('#\s*style\s*=\s*"\s*"#i', '', $cleaned);
                $cleaned = (string) preg_replace("#\\s*style\\s*=\\s*'\\s*'#i", '', $cleaned);
                $cleaned = trim($cleaned);
                if ($cleaned !== $new_desc) {
                    $stats['style_stripped']++;
                    $new_desc = $cleaned;
                }

                
                
                $headline_changed    = ($headline_length > 0 && $headline_inner !== '');
                $description_changed = ($new_desc !== $desc);
                if (!$headline_changed && !$description_changed) {
                    $stats['skipped']++;
                    continue;
                }

                if ($headline_changed) {
                    
                    
                    
                    
                    
                    $ok = self::save($taxonomy, (int) $term->term_id, array('headline' => $stripped));
                    if ($ok === false) {
                        $stats['errors']++;
                        continue;
                    }
                    $stats['headlines']++;
                }

                if ($description_changed) {
                    $upd = wp_update_term((int) $term->term_id, $taxonomy, array('description' => $new_desc));
                    if (is_wp_error($upd)) {
                        $stats['errors']++;
                        continue;
                    }
                }

                $stats['taxonomies'][$taxonomy]++;
            }
        }

        
        
        if ($nav_module) {
            add_action('created_term', array($nav_module, 'on_term_change'), 99, 3);
            add_action('edited_term',  array($nav_module, 'on_term_change'), 99, 3);
            if (method_exists($nav_module, 'generate_json')) {
                $nav_module->generate_json();
            }
        }

        update_option('aieo_dmm_category_extras_h3_migrated_at', time(), false);
        return $stats;
    }

    public function ajax_migrate_h3_headlines() {
        AIEO_DMM::verify_ajax_request();
        if (function_exists('ob_get_level') && ob_get_level()) { @ob_end_clean(); }

        
        
        
        
        
        $migrated_at = (int) get_option('aieo_dmm_category_extras_h3_migrated_at', 0);
        $force = !empty($_POST['force']);
        if ($migrated_at && !$force) {
            wp_send_json_error(array(
                'message'     => __('H3 → headline migration has already run on this site. Re-running would touch term descriptions again. Pass `force=1` to override.', 'ai-eshop-optimizer'),
                'migrated_at' => $migrated_at,
            ));
        }
        $stats = $this->run_h3_headline_migration();
        wp_send_json_success(array(
            'stats'       => $stats,
            'migrated_at' => time(),
        ));
    }

    
    public function ajax_save_settings() {
        AIEO_DMM::verify_ajax_request();
        $patch = array();

        if (isset($_POST['chips_enabled'])) {
            $patch['category_extras_chips_enabled'] = !empty($_POST['chips_enabled']);
        }
        if (isset($_POST['chips_mobile_slider'])) {
            $patch['category_extras_chips_mobile_slider'] = !empty($_POST['chips_mobile_slider']);
        }
        
        $int_fields = array(
            'chips_border_width'  => 'category_extras_chips_border_width',
            'chips_border_radius' => 'category_extras_chips_border_radius',
            'chips_padding_y'     => 'category_extras_chips_padding_y',
            'chips_padding_x'     => 'category_extras_chips_padding_x',
            'chips_gap_y'         => 'category_extras_chips_gap_y',
            'chips_gap_x'         => 'category_extras_chips_gap_x',
            'nav_border_width'    => 'navigator_border_width',
            'nav_border_radius'   => 'navigator_border_radius',
        );
        foreach ($int_fields as $post_key => $setting_key) {
            if (isset($_POST[$post_key]) && $_POST[$post_key] !== '') {
                $patch[$setting_key] = (int) $_POST[$post_key];
            }
        }
        $color_fields = array(
            'chips_border_color' => 'category_extras_chips_border_color',
            'chips_text_color'   => 'category_extras_chips_text_color',
            'nav_border_color'   => 'navigator_border_color',
        );
        foreach ($color_fields as $post_key => $setting_key) {
            if (isset($_POST[$post_key])) {
                $patch[$setting_key] = sanitize_text_field( wp_unslash( (string) $_POST[$post_key] ) );
            }
        }

        if (!empty($patch)) {
            AIEO_DMM_Settings::update($patch);
        }
        wp_send_json_success(array(
            'chips_enabled' => self::chips_enabled() ? 1 : 0,
        ));
    }

    
    public function ajax_save() {
        AIEO_DMM::verify_ajax_request();
        $taxonomy = isset($_POST['taxonomy']) ? sanitize_key( wp_unslash( (string) $_POST['taxonomy'] ) ) : '';
        $term_id  = isset($_POST['term_id'])  ? (int) $_POST['term_id'] : 0;
        if (!$taxonomy || !$term_id) {
            wp_send_json_error(array('message' => __('Missing taxonomy/term_id.', 'ai-eshop-optimizer')));
        }
        $payload = array();
        foreach (array('headline','description','seo_title','sidebar_text','footer_text','columns','disable_nested','image_mobile_id','image_desktop_id') as $k) {
            if (isset($_POST[$k])) {
                $payload[$k] = is_array($_POST[$k])
                    ? map_deep(wp_unslash($_POST[$k]), 'sanitize_text_field')
                    : sanitize_textarea_field(wp_unslash($_POST[$k]));
            }
        }
        if (isset($_POST['landing_pages']) && is_array($_POST['landing_pages'])) {
            $payload['landing_pages'] = map_deep(wp_unslash($_POST['landing_pages']), 'sanitize_text_field');
        }
        $ok = self::save($taxonomy, $term_id, $payload);
        if ($ok === false) {
            wp_send_json_error(array('message' => __('Save failed.', 'ai-eshop-optimizer')));
        }
        wp_send_json_success(array('row' => self::get($taxonomy, $term_id)));
    }

    
    
    

    
    public function render_term_extras($term, $taxonomy) {
        if (!is_object($term) || !isset($term->term_id)) return;

        $row = self::get($taxonomy, (int) $term->term_id);
        if (!$row) {
            $row = array(
                'headline'             => '',
                'image_mobile_id'      => 0,
                'image_desktop_id'     => 0,
                'seo_title'            => '',
                'sidebar_text'         => '',
                'footer_text'          => '',
                'columns'              => null,
                'disable_nested'       => 0,
                'template'             => 'default',
                'pro_only'             => 0,
                'reference_quantity'   => '',
                'synonyms'             => '',
                'season_start'         => '',
                'season_end'           => '',
                'season_midpoint'      => '',
                'related_brand_ids'    => array(),
                'related_post_ids'     => array(),
                'related_campaign_ids' => array(),
                'google_category_id'   => null,
                'google_category_path' => '',
                'facebook_category_id'   => null,
                'facebook_category_path' => '',
            );
        }

        $mobile_url  = $row['image_mobile_id']  ? wp_get_attachment_image_url((int) $row['image_mobile_id'],  'medium') : '';
        $desktop_url = $row['image_desktop_id'] ? wp_get_attachment_image_url((int) $row['image_desktop_id'], 'medium') : '';

        
        
        $brand_chips    = $this->resolve_term_chips((array) $row['related_brand_ids'], 'product_brand');
        $post_chips     = $this->resolve_post_chips((array) $row['related_post_ids']);
        $campaign_chips = $this->resolve_post_chips((array) $row['related_campaign_ids']);

        
        $allow_brand_picker = ($taxonomy !== 'product_brand') && taxonomy_exists('product_brand');
        ?>
        <tr class="form-field aieo-ce-panel-row">
            <th colspan="2" style="padding:0;">
                <div class="aieo-ce-panel" data-aieo-ce-panel data-term-id="<?php echo (int) $term->term_id; ?>" data-taxonomy="<?php echo esc_attr($taxonomy); ?>">
                    <?php wp_nonce_field('aieo_ce_term_edit_' . (int) $term->term_id, 'aieo_ce_nonce'); ?>
                    <div class="aieo-ce-panel-head">
                        <h2><?php esc_html_e('AIEO Category Extras', 'ai-eshop-optimizer'); ?></h2>
                        <p class="description"><?php esc_html_e('Stored in wp_aieo_dmm_category_extras. Replaces the Pods + om-category-essentials sources for these fields. Other legacy fields above keep their existing storage during the transition.', 'ai-eshop-optimizer'); ?></p>
                    </div>

                    <nav class="aieo-ce-tabs" role="tablist">
                        <button type="button" class="aieo-ce-tab is-active" data-aieo-ce-tab="hero"    role="tab" aria-selected="true"><?php esc_html_e('Hero', 'ai-eshop-optimizer'); ?></button>
                        <button type="button" class="aieo-ce-tab"           data-aieo-ce-tab="seo"     role="tab" aria-selected="false"><?php esc_html_e('SEO', 'ai-eshop-optimizer'); ?></button>
                        <button type="button" class="aieo-ce-tab"           data-aieo-ce-tab="layout"  role="tab" aria-selected="false"><?php esc_html_e('Layout', 'ai-eshop-optimizer'); ?></button>
                        <button type="button" class="aieo-ce-tab"           data-aieo-ce-tab="catalog" role="tab" aria-selected="false"><?php esc_html_e('Catalog', 'ai-eshop-optimizer'); ?></button>
                        <button type="button" class="aieo-ce-tab"           data-aieo-ce-tab="related" role="tab" aria-selected="false"><?php esc_html_e('Related', 'ai-eshop-optimizer'); ?></button>
                        <?php if ($taxonomy === 'product_cat'): ?>
                        <button type="button" class="aieo-ce-tab"           data-aieo-ce-tab="marketplace" role="tab" aria-selected="false"><?php esc_html_e('Marketplace', 'ai-eshop-optimizer'); ?></button>
                        <?php endif; ?>
                    </nav>

                    <!-- ── Tab: Hero ─────────────────────────────────────── -->
                    <div class="aieo-ce-tabpanel is-active" data-aieo-ce-pane="hero" role="tabpanel">
                        <div class="aieo-ce-grid-2">
                            <div class="aieo-ce-field aieo-ce-image-field" data-aieo-ce-image="mobile">
                                <label><?php esc_html_e('Mobile image', 'ai-eshop-optimizer'); ?></label>
                                <div class="aieo-ce-image-preview"<?php echo $mobile_url ? '' : ' style="display:none;"'; ?>>
                                    <?php if ($mobile_url): ?><img src="<?php echo esc_url($mobile_url); ?>" alt="" /><?php endif; ?>
                                </div>
                                <input type="hidden" name="aieo_ce[image_mobile_id]" value="<?php echo (int) $row['image_mobile_id']; ?>" />
                                <button type="button" class="button aieo-ce-image-pick"><?php esc_html_e('Select / change', 'ai-eshop-optimizer'); ?></button>
                                <button type="button" class="button-link aieo-ce-image-clear"<?php echo $mobile_url ? '' : ' style="display:none;"'; ?>><?php esc_html_e('Remove', 'ai-eshop-optimizer'); ?></button>
                                <p class="description"><?php esc_html_e('Shown on small viewports. Recommended portrait or square.', 'ai-eshop-optimizer'); ?></p>
                            </div>
                            <div class="aieo-ce-field aieo-ce-image-field" data-aieo-ce-image="desktop">
                                <label><?php esc_html_e('Desktop image (banner)', 'ai-eshop-optimizer'); ?></label>
                                <div class="aieo-ce-image-preview"<?php echo $desktop_url ? '' : ' style="display:none;"'; ?>>
                                    <?php if ($desktop_url): ?><img src="<?php echo esc_url($desktop_url); ?>" alt="" /><?php endif; ?>
                                </div>
                                <input type="hidden" name="aieo_ce[image_desktop_id]" value="<?php echo (int) $row['image_desktop_id']; ?>" />
                                <button type="button" class="button aieo-ce-image-pick"><?php esc_html_e('Select / change', 'ai-eshop-optimizer'); ?></button>
                                <button type="button" class="button-link aieo-ce-image-clear"<?php echo $desktop_url ? '' : ' style="display:none;"'; ?>><?php esc_html_e('Remove', 'ai-eshop-optimizer'); ?></button>
                                <p class="description"><?php esc_html_e('Shown on wide viewports as the hero banner.', 'ai-eshop-optimizer'); ?></p>
                            </div>
                        </div>
                        <div class="aieo-ce-field aieo-ce-rich-field">
                            <label for="aieo_ce_headline"><?php esc_html_e('Headline (left side of hero)', 'ai-eshop-optimizer'); ?></label>
                            <?php wp_editor(
                                (string) $row['headline'],
                                'aieo_ce_headline',
                                array(
                                    'textarea_name' => 'aieo_ce[headline]',
                                    'textarea_rows' => 5,
                                    'media_buttons' => true,
                                    'teeny'         => false,
                                    'quicktags'     => true,
                                )
                            ); ?>
                            <p class="description"><?php esc_html_e('Short, marketing-style headline rendered on the left of the hero. The right side uses the WP term description (above).', 'ai-eshop-optimizer'); ?></p>
                        </div>
                    </div>

                    <!-- ── Tab: SEO ──────────────────────────────────────── -->
                    <div class="aieo-ce-tabpanel" data-aieo-ce-pane="seo" role="tabpanel">
                        <div class="aieo-ce-field">
                            <label for="aieo-ce-seo-title"><?php esc_html_e('SEO Title (H1)', 'ai-eshop-optimizer'); ?></label>
                            <input type="text" id="aieo-ce-seo-title" name="aieo_ce[seo_title]" value="<?php echo esc_attr((string) $row['seo_title']); ?>" class="large-text" />
                            <p class="description"><?php esc_html_e('Custom H1 / page title. Leave empty to use the term name.', 'ai-eshop-optimizer'); ?></p>
                        </div>
                        <div class="aieo-ce-field aieo-ce-rich-field">
                            <label for="aieo_ce_sidebar_text"><?php esc_html_e('Sidebar text (top of archive — SEO header)', 'ai-eshop-optimizer'); ?></label>
                            <?php wp_editor(
                                (string) $row['sidebar_text'],
                                'aieo_ce_sidebar_text',
                                array(
                                    'textarea_name' => 'aieo_ce[sidebar_text]',
                                    'textarea_rows' => 8,
                                    'media_buttons' => true,
                                    'teeny'         => false,
                                    'quicktags'     => true,
                                )
                            ); ?>
                            <p class="description"><?php esc_html_e('Rich content rendered alongside / above the product grid. Was Pods "Pods Footer Text" / om-category-essentials sidebar_text.', 'ai-eshop-optimizer'); ?></p>
                        </div>
                        <div class="aieo-ce-field aieo-ce-rich-field">
                            <label for="aieo_ce_footer_text"><?php esc_html_e('Footer text (bottom of archive — SEO footer)', 'ai-eshop-optimizer'); ?></label>
                            <?php wp_editor(
                                (string) $row['footer_text'],
                                'aieo_ce_footer_text',
                                array(
                                    'textarea_name' => 'aieo_ce[footer_text]',
                                    'textarea_rows' => 12,
                                    'media_buttons' => true,
                                    'teeny'         => false,
                                    'quicktags'     => true,
                                )
                            ); ?>
                            <p class="description"><?php esc_html_e('Rich content rendered below the product grid. Migrated from om-category-essentials footer_text.', 'ai-eshop-optimizer'); ?></p>
                        </div>
                    </div>

                    <!-- ── Tab: Layout ───────────────────────────────────── -->
                    <div class="aieo-ce-tabpanel" data-aieo-ce-pane="layout" role="tabpanel">
                        <div class="aieo-ce-field">
                            <label for="aieo-ce-template"><?php esc_html_e('Archive template', 'ai-eshop-optimizer'); ?></label>
                            <?php $current_tpl = isset($row['template']) ? (string) $row['template'] : 'default'; ?>
                            <select id="aieo-ce-template" name="aieo_ce[template]">
                                <?php foreach (self::templates() as $slug => $label): ?>
                                    <option value="<?php echo esc_attr($slug); ?>"<?php selected($current_tpl, $slug); ?>><?php echo esc_html($label); ?></option>
                                <?php endforeach; ?>
                            </select>
                            <p class="description"><?php esc_html_e('Which renderer the front-end uses for this term\'s archive. The Phase 3 [aieo_category_hero] block honours this. Theme code can register more templates via the aieo_dmm_category_extras_templates filter.', 'ai-eshop-optimizer'); ?></p>
                        </div>
                        <div class="aieo-ce-grid-2">
                            <div class="aieo-ce-field">
                                <label for="aieo-ce-columns"><?php esc_html_e('Subcategory columns', 'ai-eshop-optimizer'); ?></label>
                                <select id="aieo-ce-columns" name="aieo_ce[columns]">
                                    <option value=""<?php selected((string) ($row['columns'] ?? ''), ''); ?>><?php esc_html_e('Default', 'ai-eshop-optimizer'); ?></option>
                                    <?php for ($i = 1; $i <= 6; $i++): ?>
                                        <option value="<?php echo esc_attr($i); ?>"<?php selected((int) ($row['columns'] ?? 0), $i); ?>><?php echo esc_html($i); ?></option>
                                    <?php endfor; ?>
                                </select>
                                <p class="description"><?php esc_html_e('Number of columns for the subcategory grid.', 'ai-eshop-optimizer'); ?></p>
                            </div>
                            <div class="aieo-ce-field">
                                <label>
                                    <input type="checkbox" name="aieo_ce[disable_nested]" value="1"<?php checked(!empty($row['disable_nested'])); ?> />
                                    <?php esc_html_e('Disable nested categories', 'ai-eshop-optimizer'); ?>
                                </label>
                                <p class="description"><?php esc_html_e('Hide the subcategory grid for this term. Products still render normally.', 'ai-eshop-optimizer'); ?></p>
                            </div>
                        </div>
                    </div>

                    <!-- ── Tab: Catalog ──────────────────────────────────── -->
                    <div class="aieo-ce-tabpanel" data-aieo-ce-pane="catalog" role="tabpanel">
                        <div class="aieo-ce-grid-2">
                            <div class="aieo-ce-field">
                                <label>
                                    <input type="checkbox" name="aieo_ce[pro_only]" value="1"<?php checked(!empty($row['pro_only'])); ?> />
                                    <?php esc_html_e('Professionals only', 'ai-eshop-optimizer'); ?>
                                </label>
                                <p class="description"><?php esc_html_e('Hide / restrict the category to professional users.', 'ai-eshop-optimizer'); ?></p>
                            </div>
                            <div class="aieo-ce-field">
                                <label for="aieo-ce-reference-quantity"><?php esc_html_e('Reference quantity', 'ai-eshop-optimizer'); ?></label>
                                <input type="number" min="0" step="1" id="aieo-ce-reference-quantity" name="aieo_ce[reference_quantity]" value="<?php echo $row['reference_quantity'] !== null && $row['reference_quantity'] !== '' ? (int) $row['reference_quantity'] : ''; ?>" class="small-text" />
                                <p class="description"><?php esc_html_e('Sales count used to predict next sell-out.', 'ai-eshop-optimizer'); ?></p>
                            </div>
                        </div>
                        <div class="aieo-ce-field">
                            <label for="aieo-ce-synonyms"><?php esc_html_e('Synonyms', 'ai-eshop-optimizer'); ?></label>
                            <textarea id="aieo-ce-synonyms" name="aieo_ce[synonyms]" rows="2" class="large-text"><?php echo esc_textarea((string) $row['synonyms']); ?></textarea>
                            <p class="description"><?php esc_html_e('Alternative names / search aliases.', 'ai-eshop-optimizer'); ?></p>
                        </div>
                        <div class="aieo-ce-grid-3">
                            <div class="aieo-ce-field">
                                <label for="aieo-ce-season-start"><?php esc_html_e('Season start', 'ai-eshop-optimizer'); ?></label>
                                <input type="date" id="aieo-ce-season-start" name="aieo_ce[season_start]" value="<?php echo esc_attr((string) $row['season_start']); ?>" />
                            </div>
                            <div class="aieo-ce-field">
                                <label for="aieo-ce-season-mid"><?php esc_html_e('Season midpoint', 'ai-eshop-optimizer'); ?></label>
                                <input type="date" id="aieo-ce-season-mid" name="aieo_ce[season_midpoint]" value="<?php echo esc_attr((string) $row['season_midpoint']); ?>" />
                            </div>
                            <div class="aieo-ce-field">
                                <label for="aieo-ce-season-end"><?php esc_html_e('Season end', 'ai-eshop-optimizer'); ?></label>
                                <input type="date" id="aieo-ce-season-end" name="aieo_ce[season_end]" value="<?php echo esc_attr((string) $row['season_end']); ?>" />
                            </div>
                        </div>
                    </div>

                    <!-- ── Tab: Related ──────────────────────────────────── -->
                    <div class="aieo-ce-tabpanel" data-aieo-ce-pane="related" role="tabpanel">
                        <?php
                        
                        
                        
                        
                        
                        
                        if (class_exists('AIEO_DMM_Related_Tab')) {
                            AIEO_DMM_Related_Tab::render($taxonomy, (int) $term->term_id);
                        } else {
                            echo '<p>' . esc_html__('Related-tab component unavailable.', 'ai-eshop-optimizer') . '</p>';
                        }
                        ?>
                    </div>

                    <?php if ($taxonomy === 'product_cat'): ?>
                    <!-- ── Tab: Marketplace ─────────────────────────────────
                         Google + Facebook product-taxonomy mappings. Saved
                         values flow into wp_aieo_dmm_category_extras and
                         are propagated into wp_aieo_core_product_vitals by
                         the hourly data-prep stage `propagate_marketplace_categories`,
                         so feed builders read them as pure column lookups.
                    -->
                    <div class="aieo-ce-tabpanel" data-aieo-ce-pane="marketplace" role="tabpanel">
                        <p class="description" style="margin-bottom:14px;">
                            <?php esc_html_e('Map this category to the official Google + Facebook product taxonomies. Every product in this category (and its children, if no override is set lower) inherits the mapping. Google\'s taxonomy is accepted by Facebook Catalog, Google Shopping, and Facebook Marketplace. Facebook\'s separate taxonomy is only needed if you list on Facebook Marketplace.', 'ai-eshop-optimizer'); ?>
                        </p>
                        <div class="aieo-ce-marketplace-tax"
                             data-source="google"
                             data-current-id="<?php echo (int) ($row['google_category_id'] ?? 0); ?>"
                             data-current-path="<?php echo esc_attr((string) ($row['google_category_path'] ?? '')); ?>">
                            <h3 style="margin:6px 0 4px;"><?php esc_html_e('Google Product Taxonomy', 'ai-eshop-optimizer'); ?></h3>
                            <p class="description" style="margin-top:0;">
                                <?php esc_html_e('Used by both Google Shopping and Facebook Catalog feeds. Browse the tree or type to search.', 'ai-eshop-optimizer'); ?>
                            </p>
                            <input type="hidden" name="aieo_ce[google_category_id]"   value="<?php echo (int) ($row['google_category_id'] ?? 0); ?>" class="aieo-ce-mt-id" />
                            <input type="hidden" name="aieo_ce[google_category_path]" value="<?php echo esc_attr((string) ($row['google_category_path'] ?? '')); ?>" class="aieo-ce-mt-path" />
                            <div class="aieo-ce-mt-current" style="margin:6px 0; padding:6px 10px; background:#f6f7f7; border-left:3px solid #2271b1; font-size:13px;">
                                <strong><?php esc_html_e('Current:', 'ai-eshop-optimizer'); ?></strong>
                                <span class="aieo-ce-mt-current-path"><?php echo esc_html((string) ($row['google_category_path'] ?? '') ?: __('(not set)', 'ai-eshop-optimizer')); ?></span>
                                <button type="button" class="button-link aieo-ce-mt-clear" style="margin-left:8px;<?php echo empty($row['google_category_id']) ? 'display:none;' : ''; ?>"><?php esc_html_e('Clear', 'ai-eshop-optimizer'); ?></button>
                            </div>
                            <input type="search" class="aieo-ce-mt-search large-text" placeholder="<?php esc_attr_e('Type to search — e.g. Lipstick, Shampoo, Hair Care', 'ai-eshop-optimizer'); ?>" />
                            <div class="aieo-ce-mt-results" style="margin-top:6px;"></div>
                            <div class="aieo-ce-mt-cascade" style="display:flex; gap:8px; margin-top:8px; flex-wrap:wrap;"></div>
                        </div>

                        <div class="aieo-ce-marketplace-tax" style="margin-top:24px;"
                             data-source="facebook"
                             data-current-id="<?php echo (int) ($row['facebook_category_id'] ?? 0); ?>"
                             data-current-path="<?php echo esc_attr((string) ($row['facebook_category_path'] ?? '')); ?>">
                            <h3 style="margin:6px 0 4px;"><?php esc_html_e('Facebook Marketplace Taxonomy', 'ai-eshop-optimizer'); ?></h3>
                            <p class="description" style="margin-top:0;">
                                <?php esc_html_e('Optional. Only needed if you list on Facebook Marketplace (the C2C/B2C marketplace inside Facebook). Facebook Catalog uses the Google taxonomy above.', 'ai-eshop-optimizer'); ?>
                            </p>
                            <input type="hidden" name="aieo_ce[facebook_category_id]"   value="<?php echo (int) ($row['facebook_category_id'] ?? 0); ?>" class="aieo-ce-mt-id" />
                            <input type="hidden" name="aieo_ce[facebook_category_path]" value="<?php echo esc_attr((string) ($row['facebook_category_path'] ?? '')); ?>" class="aieo-ce-mt-path" />
                            <div class="aieo-ce-mt-current" style="margin:6px 0; padding:6px 10px; background:#f6f7f7; border-left:3px solid #2271b1; font-size:13px;">
                                <strong><?php esc_html_e('Current:', 'ai-eshop-optimizer'); ?></strong>
                                <span class="aieo-ce-mt-current-path"><?php echo esc_html((string) ($row['facebook_category_path'] ?? '') ?: __('(not set)', 'ai-eshop-optimizer')); ?></span>
                                <button type="button" class="button-link aieo-ce-mt-clear" style="margin-left:8px;<?php echo empty($row['facebook_category_id']) ? 'display:none;' : ''; ?>"><?php esc_html_e('Clear', 'ai-eshop-optimizer'); ?></button>
                            </div>
                            <input type="search" class="aieo-ce-mt-search large-text" placeholder="<?php esc_attr_e('Type to search — e.g. Cosmetics, Skin Care', 'ai-eshop-optimizer'); ?>" />
                            <div class="aieo-ce-mt-results" style="margin-top:6px;"></div>
                            <div class="aieo-ce-mt-cascade" style="display:flex; gap:8px; margin-top:8px; flex-wrap:wrap;"></div>
                        </div>
                    </div>
                    <?php endif; ?>
                </div>
            </th>
        </tr>
        <?php
    }

    
    private function resolve_post_chips(array $ids) {
        $ids  = array_filter(array_map('intval', $ids));
        if (empty($ids)) return array();
        $out  = array();
        foreach ($ids as $pid) {
            $title = get_the_title($pid);
            if (!$title) $title = '#' . $pid;
            $out[] = array('id' => $pid, 'label' => $title);
        }
        return $out;
    }

    
    private function resolve_term_chips(array $ids, $taxonomy) {
        $ids = array_filter(array_map('intval', $ids));
        if (empty($ids)) return array();
        $out = array();
        foreach ($ids as $tid) {
            $term = get_term($tid, $taxonomy);
            $label = ($term && !is_wp_error($term)) ? $term->name : ('#' . $tid);
            $out[] = array('id' => $tid, 'label' => $label);
        }
        return $out;
    }

    
    public function save_term_extras($term_id, $tt_id = 0) {
        $term_id = (int) $term_id;
        if (!$term_id) return;
        if (empty($_POST['aieo_ce_nonce']) || !wp_verify_nonce(
            sanitize_key(wp_unslash($_POST['aieo_ce_nonce'])),
            'aieo_ce_term_edit_' . $term_id
        )) {
            return;
        }
        if (empty($_POST['aieo_ce']) || !is_array($_POST['aieo_ce'])) return;

        $term = get_term($term_id);
        if (!$term || is_wp_error($term)) return;
        if (!in_array($term->taxonomy, $this->managed_taxonomies(), true)) return;

        $raw = map_deep(wp_unslash($_POST['aieo_ce']), 'sanitize_textarea_field');
        $patch = array();

        
        if (isset($raw['headline']))         $patch['headline']         = (string) $raw['headline'];
        if (isset($raw['image_mobile_id']))  $patch['image_mobile_id']  = (int) $raw['image_mobile_id'];
        if (isset($raw['image_desktop_id'])) $patch['image_desktop_id'] = (int) $raw['image_desktop_id'];

        
        if (isset($raw['seo_title']))        $patch['seo_title']        = (string) $raw['seo_title'];
        if (isset($raw['sidebar_text']))     $patch['sidebar_text']     = (string) $raw['sidebar_text'];
        if (isset($raw['footer_text']))      $patch['footer_text']      = (string) $raw['footer_text'];

        
        if (isset($raw['template']))         $patch['template']         = (string) $raw['template'];
        
        
        $patch['columns']        = (isset($raw['columns']) && $raw['columns'] !== '') ? (int) $raw['columns'] : null;
        $patch['disable_nested'] = !empty($raw['disable_nested']) ? 1 : 0;

        
        $patch['pro_only']             = !empty($raw['pro_only']) ? 1 : 0;
        $patch['reference_quantity']   = (isset($raw['reference_quantity']) && $raw['reference_quantity'] !== '') ? (int) $raw['reference_quantity'] : null;
        if (isset($raw['synonyms']))   $patch['synonyms']   = (string) $raw['synonyms'];
        foreach (array('season_start', 'season_end', 'season_midpoint') as $k) {
            if (array_key_exists($k, $raw)) $patch[$k] = (string) $raw[$k];
        }

        
        
        
        
        

        
        
        
        
        if ($term->taxonomy === 'product_cat') {
            foreach (array('google', 'facebook') as $source) {
                $id_key   = $source . '_category_id';
                $path_key = $source . '_category_path';
                if (array_key_exists($id_key, $raw) || array_key_exists($path_key, $raw)) {
                    $id   = isset($raw[$id_key])   ? (int) $raw[$id_key]   : 0;
                    $path = isset($raw[$path_key]) ? (string) $raw[$path_key] : '';
                    $patch[$id_key]   = $id   > 0 ? $id   : null;
                    $patch[$path_key] = $path !== '' ? $path : null;
                }
            }
        }

        self::save($term->taxonomy, $term_id, $patch);
    }

    
    public function enqueue_term_admin_assets($hook) {
        
        
        
        
        if ($hook !== 'term.php') return;
        $tax = isset($_GET['taxonomy']) ? sanitize_key(wp_unslash($_GET['taxonomy'])) : '';
        
        
        
        
        if (!$tax || !in_array($tax, $this->description_editor_taxonomies(), true)) return;

        wp_enqueue_media();
        
        
        
        
        if (function_exists('wp_enqueue_editor')) {
            wp_enqueue_editor();
        }
        wp_enqueue_style(
            'aieo-dmm-category-extras-admin',
            AIEO_URI . 'assets/css/aieo-dmm-category-extras-admin.css',
            array(),
            AIEO_VERSION
        );
        wp_enqueue_script(
            'aieo-dmm-category-extras-admin',
            AIEO_URI . 'assets/js/aieo-dmm-category-extras-admin.js',
            array('jquery'),
            AIEO_VERSION,
            true
        );
        wp_localize_script('aieo-dmm-category-extras-admin', 'aieoCategoryExtras', array(
            'ajaxurl' => admin_url('admin-ajax.php'),
            'nonce'   => wp_create_nonce(AIEO_DMM::NONCE_ACTION),
            'i18n'    => array(
                'noResults'   => __('No matches.', 'ai-eshop-optimizer'),
                'searching'   => __('Searching…', 'ai-eshop-optimizer'),
                'pickImage'   => __('Select image', 'ai-eshop-optimizer'),
                'useImage'    => __('Use this image', 'ai-eshop-optimizer'),
                'notSet'      => __('(not set)', 'ai-eshop-optimizer'),
                'rootLabel'   => __('Top level', 'ai-eshop-optimizer'),
                'pickPrompt'  => __('— pick —', 'ai-eshop-optimizer'),
                'picked'      => __('Picked', 'ai-eshop-optimizer'),
                'browseAria'  => __('Browse taxonomy level', 'ai-eshop-optimizer'),
            ),
            'mt' => array(
                'actionBrowse' => 'aieo_dmm_marketplace_tax_browse',
                'actionSearch' => 'aieo_dmm_marketplace_tax_search',
            ),
        ));
    }

    
    public function ajax_search() {
        AIEO_DMM::verify_ajax_request();
        $source = isset($_POST['source']) ? sanitize_text_field(wp_unslash($_POST['source'])) : '';
        $q      = isset($_POST['q'])      ? sanitize_text_field(wp_unslash($_POST['q']))      : '';

        $allowed_post_types = (array) apply_filters(
            'aieo_dmm_category_extras_search_post_types',
            array('post', 'campaign', 'page')
        );
        $allowed_taxonomies = (array) apply_filters(
            'aieo_dmm_category_extras_search_taxonomies',
            array('product_brand', 'product_cat')
        );

        $items = array();

        if (strpos($source, 'post:') === 0) {
            $type = sanitize_key(substr($source, 5));
            if (in_array($type, $allowed_post_types, true)) {
                $posts = get_posts(array(
                    'post_type'      => $type,
                    'post_status'    => 'publish',
                    's'              => $q,
                    'posts_per_page' => 20,
                    'orderby'        => 'date',
                    'order'          => 'DESC',
                    'no_found_rows'  => true,
                ));
                foreach ($posts as $p) {
                    $items[] = array('id' => (int) $p->ID, 'label' => get_the_title($p) ?: '#' . $p->ID);
                }
            }
        } elseif (strpos($source, 'taxonomy:') === 0) {
            $tax = sanitize_key(substr($source, 9));
            if (in_array($tax, $allowed_taxonomies, true) && taxonomy_exists($tax)) {
                $terms = get_terms(array(
                    'taxonomy'   => $tax,
                    'hide_empty' => false,
                    'name__like' => $q,
                    'number'     => 20,
                    'orderby'    => 'name',
                    'order'      => 'ASC',
                ));
                if (!is_wp_error($terms)) {
                    foreach ($terms as $t) {
                        $items[] = array('id' => (int) $t->term_id, 'label' => $t->name);
                    }
                }
            }
        }

        wp_send_json_success(array('items' => $items));
    }

    
    public function ajax_marketplace_tax_browse() {
        AIEO_DMM::verify_ajax_request();
        $source    = isset($_POST['source'])    ? sanitize_key(wp_unslash($_POST['source']))         : '';
        $parent_id = isset($_POST['parent_id']) ? (int) $_POST['parent_id']                          : 0;
        if (!in_array($source, array('google', 'facebook'), true)) {
            wp_send_json_error(array('message' => __('Bad source.', 'ai-eshop-optimizer')));
        }
        if (!class_exists('AIEO_DMM_Marketplace_Taxonomy')) {
            wp_send_json_error(array('message' => __('Marketplace taxonomy helper unavailable.', 'ai-eshop-optimizer')));
        }
        $children = AIEO_DMM_Marketplace_Taxonomy::get_children($source, $parent_id);
        $out = array();
        foreach ($children as $n) {
            $has_kids = !empty(AIEO_DMM_Marketplace_Taxonomy::get_children($source, (int) $n['i']));
            $out[] = array(
                'id'           => (int) $n['i'],
                'name'         => (string) $n['n'],
                'path'         => (string) $n['h'],
                'has_children' => $has_kids,
                'depth'        => (int) $n['d'],
            );
        }
        wp_send_json_success(array('nodes' => $out));
    }

    
    public function ajax_marketplace_tax_search() {
        AIEO_DMM::verify_ajax_request();
        $source = isset($_POST['source']) ? sanitize_key(wp_unslash($_POST['source'])) : '';
        $q      = isset($_POST['q'])      ? sanitize_text_field(wp_unslash($_POST['q'])) : '';
        if (!in_array($source, array('google', 'facebook'), true)) {
            wp_send_json_error(array('message' => __('Bad source.', 'ai-eshop-optimizer')));
        }
        if (!class_exists('AIEO_DMM_Marketplace_Taxonomy')) {
            wp_send_json_error(array('message' => __('Marketplace taxonomy helper unavailable.', 'ai-eshop-optimizer')));
        }
        $hits = AIEO_DMM_Marketplace_Taxonomy::search($source, $q, 25);
        $out = array();
        foreach ($hits as $n) {
            
            
            
            
            
            
            
            $out[] = array(
                'id'             => (int) $n['i'],
                'name'           => (string) $n['n'],
                'path'           => (string) $n['h'],
                'site_path'      => (string) ($n['site_path'] ?? $n['h']),
                'matched_locale' => (string) ($n['matched_locale'] ?? ''),
                'depth'          => (int) $n['d'],
            );
        }
        wp_send_json_success(array('hits' => $out));
    }

    
    
    

    
    public static function get_template($taxonomy, $term_id) {
        $row = self::get($taxonomy, $term_id);
        if (!$row || empty($row['template'])) return 'default';
        $allowed = array_keys(self::templates());
        return in_array($row['template'], $allowed, true) ? $row['template'] : 'default';
    }

    
    public static function should_hide_products($taxonomy, $term_id) {
        $tpl = self::get_template($taxonomy, $term_id);
        return in_array($tpl, array('landing', 'hidden'), true);
    }

    
    private function current_term() {
        if (!function_exists('is_tax') && !function_exists('is_category')) return null;
        $obj = get_queried_object();
        if (!$obj || empty($obj->term_id) || empty($obj->taxonomy)) return null;
        if (!in_array($obj->taxonomy, $this->managed_taxonomies(), true)) return null;
        return $obj;
    }

    
    public function shortcode_category_hero($atts = array()) {
        $atts = shortcode_atts(array(
            'taxonomy' => '',
            'term_id'  => 0,
            'template' => '',  
        ), (array) $atts, 'aieo_category_hero');

        if (empty($atts['taxonomy']) || empty($atts['term_id'])) {
            $term = $this->current_term();
            if (!$term) return '';
            $atts['taxonomy'] = $term->taxonomy;
            $atts['term_id']  = (int) $term->term_id;
        }
        return $this->render_hero($atts['taxonomy'], (int) $atts['term_id'], (string) $atts['template']);
    }

    
    public function register_block() {
        if (!function_exists('register_block_type')) return;
        register_block_type('aieo/category-hero', array(
            'title'    => __('AIEO Category Hero', 'ai-eshop-optimizer'),
            'category' => 'widgets',
            'icon'     => 'cover-image',
            'attributes' => array(
                'taxonomy' => array('type' => 'string', 'default' => ''),
                'termId'   => array('type' => 'number', 'default' => 0),
                'template' => array('type' => 'string', 'default' => ''),
                'align'    => array('type' => 'string'),
            ),
            'supports' => array(
                'align' => array('wide', 'full'),
            ),
            'render_callback' => array($this, 'render_block_callback'),
        ));
        register_block_type('aieo/category-chips', array(
            'title'    => __('AIEO Category Chips', 'ai-eshop-optimizer'),
            'category' => 'widgets',
            'icon'     => 'tag',
            'attributes' => array(
                'taxonomy' => array('type' => 'string', 'default' => ''),
                'termId'   => array('type' => 'number', 'default' => 0),
                'align'    => array('type' => 'string'),
            ),
            'supports' => array(
                'align' => array('wide', 'full'),
            ),
            'render_callback' => array($this, 'render_chips_block_callback'),
        ));

        
        
        
        $ph_rel  = 'assets/js/aieo-page-hero-editor.js';
        $ph_path = defined('AIEO_DIR') ? AIEO_DIR . $ph_rel : '';
        $ph_url  = defined('AIEO_FILE') ? plugins_url($ph_rel, AIEO_FILE) : '';
        $ph_ver  = ($ph_path && file_exists($ph_path)) ? (string) filemtime($ph_path) : (defined('AIEO_VERSION') ? AIEO_VERSION : false);
        if ($ph_path && file_exists($ph_path)) {
            wp_register_script('aieo-page-hero-editor', $ph_url, array('wp-blocks', 'wp-block-editor', 'wp-components', 'wp-element', 'wp-i18n', 'wp-server-side-render'), $ph_ver, true);
        }
        
        
        if (!wp_style_is('aieo-dmm-category-extras', 'registered')) {
            $css_rel  = 'assets/css/aieo-dmm-category-extras.css';
            $css_path = defined('AIEO_DIR') ? AIEO_DIR . $css_rel : '';
            $css_ver  = ($css_path && file_exists($css_path)) ? (string) filemtime($css_path) : (defined('AIEO_VERSION') ? AIEO_VERSION : '1.0');
            wp_register_style('aieo-dmm-category-extras', AIEO_URI . $css_rel, array(), $css_ver);
        }
        register_block_type('aieo/page-hero', array(
            'title'           => __('AIEO Page Hero', 'ai-eshop-optimizer'),
            'category'        => 'theme',
            'icon'            => 'cover-image',
            'description'     => __('Manual hero — top title, left headline, right description and desktop/mobile images. Identical look to the category hero, usable on any page.', 'ai-eshop-optimizer'),
            'keywords'        => array('hero', 'banner', 'title', 'headline', 'image'),
            'attributes'      => array(
                'topTitle'    => array('type' => 'string', 'default' => ''),
                'headline'    => array('type' => 'string', 'default' => ''),
                'description' => array('type' => 'string', 'default' => ''),
                'desktopId'   => array('type' => 'number', 'default' => 0),
                'desktopUrl'  => array('type' => 'string', 'default' => ''),
                'mobileId'    => array('type' => 'number', 'default' => 0),
                'mobileUrl'   => array('type' => 'string', 'default' => ''),
                'align'       => array('type' => 'string', 'default' => ''),
            ),
            'supports'        => array('align' => array('wide', 'full')),
            'editor_script'   => 'aieo-page-hero-editor',
            'style'           => 'aieo-dmm-category-extras',
            'editor_style'    => 'aieo-dmm-category-extras',
            'render_callback' => array($this, 'render_page_hero_block'),
        ));

        
        
        
        
        
        register_block_type('aieo/blog-hero', array(
            'title'           => __('AIEO Blog Hero (Latest Post)', 'ai-eshop-optimizer'),
            'category'        => 'theme',
            'icon'            => 'admin-post',
            'description'     => __('Featured latest-post hero with the category-hero look: a top title (default “Blog”), the newest post’s title as the headline, its excerpt as the description and its featured image — all clickable, no underline. On a blog category/tag archive it features that term’s latest post.', 'ai-eshop-optimizer'),
            'keywords'        => array('blog', 'hero', 'header', 'latest', 'post', 'archive', 'category'),
            'attributes'      => array(
                'postId'      => array('type' => 'number',  'default' => 0),
                'topTitle'    => array('type' => 'string',  'default' => ''),
                'showExcerpt' => array('type' => 'boolean', 'default' => true),
                'align'       => array('type' => 'string',  'default' => ''),
            ),
            'supports'        => array('align' => array('wide', 'full')),
            'style'           => 'aieo-dmm-category-extras',
            'editor_style'    => 'aieo-dmm-category-extras',
            'render_callback' => array($this, 'render_blog_hero_block'),
        ));

        
        
        
        register_block_type('aieo/blog-subcategories', array(
            'title'           => __('AIEO Blog Subcategories (Dropdown)', 'ai-eshop-optimizer'),
            'category'        => 'theme',
            'icon'            => 'list-view',
            'description'     => __('A dropdown of the current blog category’s subcategories, styled like the blog-post categories dropdown. Renders nothing when the category has none.', 'ai-eshop-optimizer'),
            'keywords'        => array('blog', 'category', 'subcategories', 'dropdown', 'filter'),
            'attributes'      => array(
                'categoryId' => array('type' => 'number', 'default' => 0),
                'label'      => array('type' => 'string', 'default' => ''),
            ),
            'supports'        => array('spacing' => array('margin' => true, 'padding' => true), 'typography' => array('fontSize' => true)),
            'style'           => 'aieo-dmm-category-extras',
            'editor_style'    => 'aieo-dmm-category-extras',
            'render_callback' => array($this, 'render_blog_subcategories_block'),
        ));

        
        
        
        register_block_type('aieo/category-lead', array(
            'title'           => __('AIEO Category Lead', 'ai-eshop-optimizer'),
            'category'        => 'theme',
            'icon'            => 'text',
            'description'     => __('The current category’s description, falling back to its name when it has none. For blog category archive headers.', 'ai-eshop-optimizer'),
            'keywords'        => array('category', 'blog', 'description', 'title', 'archive'),
            'attributes'      => array('categoryId' => array('type' => 'number', 'default' => 0)),
            'render_callback' => array($this, 'render_category_lead_block'),
        ));

        
        
        
        
        
        register_block_type('aieo/category-footer-hero', array(
            'title'           => __('AIEO Category Footer Hero', 'ai-eshop-optimizer'),
            'category'        => 'widgets',
            'icon'            => 'align-pull-left',
            'description'     => __('The category’s SEO footer text beside the category image (image left, rich text right) with the hero divider line. Auto-fills from the queried product category.', 'ai-eshop-optimizer'),
            'keywords'        => array('category', 'footer', 'seo', 'hero', 'archive', 'description'),
            'attributes'      => array(
                'taxonomy' => array('type' => 'string', 'default' => ''),
                'termId'   => array('type' => 'number', 'default' => 0),
                'align'    => array('type' => 'string'),
            ),
            'supports'        => array('align' => array('wide', 'full')),
            'style'           => 'aieo-dmm-category-extras',
            'editor_style'    => 'aieo-dmm-category-extras',
            'render_callback' => array($this, 'render_category_footer_hero_block'),
        ));
    }

    public function render_block_callback($attributes) {
        $tax   = isset($attributes['taxonomy']) ? sanitize_key((string) $attributes['taxonomy']) : '';
        $tid   = isset($attributes['termId'])   ? (int) $attributes['termId'] : 0;
        $tpl   = isset($attributes['template']) ? sanitize_key((string) $attributes['template']) : '';
        $align = isset($attributes['align'])    ? sanitize_key((string) $attributes['align'])    : '';

        if (!$tax || !$tid) {
            $term = $this->current_term();
            if (!$term) return '';
            $tax = $term->taxonomy;
            $tid = (int) $term->term_id;
        }
        $html = $this->render_hero($tax, $tid, $tpl);
        if ($html && in_array($align, array('wide', 'full'), true)) {
            
            
            
            $html = preg_replace(
                '/^<section class="/',
                '<section class="align' . $align . ' ',
                $html,
                1
            );
        }
        return $html;
    }

    
    public function render_page_hero_block($attributes) {
        $attributes = is_array($attributes) ? $attributes : array();
        $title    = isset($attributes['topTitle'])    ? (string) $attributes['topTitle']    : '';
        $headline = isset($attributes['headline'])    ? (string) $attributes['headline']    : '';
        $desc     = isset($attributes['description']) ? (string) $attributes['description'] : '';
        $mobile_id  = isset($attributes['mobileId'])  ? (int) $attributes['mobileId']  : 0;
        $desktop_id = isset($attributes['desktopId']) ? (int) $attributes['desktopId'] : 0;
        $align    = isset($attributes['align'])       ? sanitize_key((string) $attributes['align']) : '';

        $img_html = $this->render_responsive_image($mobile_id, $desktop_id, $title);

        
        if ($title === '' && $headline === '' && $desc === '' && $img_html === '') {
            return '';
        }

        $extra = in_array($align, array('wide', 'full'), true) ? array('align' . $align) : array();
        return $this->build_hero_html($title, $headline, $desc, $img_html, 'default', $extra);
    }

    
    public function render_blog_hero_block($attributes) {
        $attributes = is_array($attributes) ? $attributes : array();

        $post_id = isset($attributes['postId']) ? (int) $attributes['postId'] : 0;
        if ($post_id <= 0) {
            $qo = get_queried_object();
            if ($qo instanceof WP_Term && in_array($qo->taxonomy, array('category', 'post_tag'), true)) {
                $post_id = $this->latest_post_in_term($qo->taxonomy, (int) $qo->term_id);
            } else {
                $ids     = get_posts(array('post_type' => 'post', 'posts_per_page' => 1, 'post_status' => 'publish', 'ignore_sticky_posts' => true, 'no_found_rows' => true, 'fields' => 'ids'));
                $post_id = !empty($ids) ? (int) $ids[0] : 0;
            }
        }
        if ($post_id <= 0 || get_post_type($post_id) !== 'post' || get_post_status($post_id) !== 'publish') {
            return '';
        }

        
        $top = isset($attributes['topTitle']) ? trim((string) $attributes['topTitle']) : '';
        if ($top === '') {
            $qo  = get_queried_object();
            $top = ($qo instanceof WP_Term) ? (string) $qo->name : __('Blog', 'ai-eshop-optimizer');
        }

        $headline = get_the_title($post_id);

        $show_excerpt = !isset($attributes['showExcerpt']) || !empty($attributes['showExcerpt']);
        $desc = '';
        if ($show_excerpt) {
            $desc = has_excerpt($post_id)
                ? get_the_excerpt($post_id)
                : wp_trim_words(wp_strip_all_tags(strip_shortcodes((string) get_post_field('post_content', $post_id))), 30);
        }

        $link     = (string) get_permalink($post_id);
        $thumb_id = (int) get_post_thumbnail_id($post_id);
        $img_html = $thumb_id ? $this->render_responsive_image($thumb_id, $thumb_id, $headline) : '';

        $align = isset($attributes['align']) ? sanitize_key((string) $attributes['align']) : '';
        $extra = in_array($align, array('wide', 'full'), true) ? array('align' . $align) : array();

        return $this->build_hero_html($top, $headline, $desc, $img_html, 'blog', $extra, $link);
    }

    
    public function render_blog_subcategories_block($attributes) {
        $attributes = is_array($attributes) ? $attributes : array();

        $term_id = isset($attributes['categoryId']) ? (int) $attributes['categoryId'] : 0;
        if ($term_id <= 0) {
            $qo = get_queried_object();
            if ($qo instanceof WP_Term && $qo->taxonomy === 'category') { $term_id = (int) $qo->term_id; }
        }
        if ($term_id <= 0) { return ''; }

        $children = get_terms(array(
            'taxonomy'   => 'category',
            'parent'     => $term_id,
            'hide_empty' => false,
            'orderby'    => 'name',
            'order'      => 'ASC',
        ));
        
        
        if (empty($children) || is_wp_error($children)) {
            $term      = get_term($term_id, 'category');
            $parent_id = ($term && !is_wp_error($term)) ? (int) $term->parent : 0;
            if ($parent_id > 0) {
                $children = get_terms(array(
                    'taxonomy'   => 'category',
                    'parent'     => $parent_id,
                    'hide_empty' => false,
                    'orderby'    => 'name',
                    'order'      => 'ASC',
                ));
            }
        }
        if (empty($children) || is_wp_error($children)) { return ''; }

        $label = (isset($attributes['label']) && trim((string) $attributes['label']) !== '')
            ? trim((string) $attributes['label'])
            : __('Κατηγορίες', 'ai-eshop-optimizer');

        $opts = array();
        foreach ($children as $child) {
            $link = get_term_link($child);
            if (!$link || is_wp_error($link)) { continue; }
            $opts[] = array('name' => $child->name, 'url' => (string) $link);
        }
        if (empty($opts)) { return ''; }

        $wrapper = function_exists('get_block_wrapper_attributes')
            ? get_block_wrapper_attributes(array('class' => 'aieo-blog-subcats'))
            : 'class="aieo-blog-subcats"';

        
        
        $arrow     = 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2012%2012%22%20fill=%22%23222222%22%3E%3Cpath%20d=%22M6%208.5%201.5%204h9z%22/%3E%3C/svg%3E';
        $sel_style = 'appearance:none;-webkit-appearance:none;-moz-appearance:none;width:auto;max-width:100%;'
            . 'padding:7px 28px 7px 12px;border:1px solid var(--wp--preset--color--contrast,#1a1a1a);border-radius:999px;'
            . 'background-color:transparent;color:inherit;font-family:inherit;font-size:0.8rem;line-height:1.2;cursor:pointer;'
            . 'background-image:url(' . $arrow . ');background-repeat:no-repeat;background-position:right 10px center;background-size:9px 9px;';

        ob_start();
        ?>
        <div <?php echo $wrapper; ?>>
            <form class="aieo-blog-subcats__form aieo-post-related-categories__form" method="get"
                  onchange="if(this.aieoSubcat.value){window.location.href=this.aieoSubcat.value;}">
                <select name="aieoSubcat" class="aieo-blog-subcats__select aieo-post-related-categories__select" aria-label="<?php echo esc_attr($label); ?>" style="<?php echo esc_attr($sel_style); ?>">
                    <option value="" selected="selected"><?php echo esc_html($label); ?></option>
                    <?php foreach ($opts as $o) : ?>
                        <option value="<?php echo esc_url($o['url']); ?>"><?php echo esc_html($o['name']); ?></option>
                    <?php endforeach; ?>
                </select>
            </form>
        </div>
        <?php
        return (string) ob_get_clean();
    }

    
    public function render_category_lead_block($attributes) {
        $attributes = is_array($attributes) ? $attributes : array();
        $term_id = isset($attributes['categoryId']) ? (int) $attributes['categoryId'] : 0;
        if ($term_id <= 0) {
            $qo = get_queried_object();
            if ($qo instanceof WP_Term && $qo->taxonomy === 'category') { $term_id = (int) $qo->term_id; }
        }
        if ($term_id <= 0) { return ''; }

        $term = get_term($term_id, 'category');
        if (!$term || is_wp_error($term)) { return ''; }

        $desc    = trim((string) $term->description);
        $is_desc = ($desc !== '');
        $text    = $is_desc ? $desc : (string) $term->name;
        if (trim($text) === '') { return ''; }

        $wrapper = function_exists('get_block_wrapper_attributes')
            ? get_block_wrapper_attributes(array('class' => 'roosterx-blog-archive__term-description aieo-category-lead' . ($is_desc ? '' : ' aieo-category-lead--name')))
            : 'class="roosterx-blog-archive__term-description aieo-category-lead"';

        return '<div ' . $wrapper . '>' 
            . ($is_desc ? wp_kses_post(wpautop($desc)) : esc_html($text))
            . '</div>';
    }

    
    public function render_category_footer_hero_block($attributes) {
        $attributes = is_array($attributes) ? $attributes : array();

        $tax = isset($attributes['taxonomy']) ? sanitize_key((string) $attributes['taxonomy']) : '';
        $tid = isset($attributes['termId']) ? (int) $attributes['termId'] : 0;
        if (!$tax || $tid <= 0) {
            $qo = get_queried_object();
            if ($qo instanceof WP_Term) { $tax = (string) $qo->taxonomy; $tid = (int) $qo->term_id; }
        }
        if (!$tax || $tid <= 0) { return ''; }

        
        $row    = array();
        $footer = '';
        if ($tax === 'product_brand' && class_exists('AIEO_DMM_Brand_Extras_Data')) {
            $row = AIEO_DMM_Brand_Extras_Data::get($tax, $tid); $footer = (string) (is_array($row) ? ($row['footer_text'] ?? '') : '');
        } elseif ($tax === 'pa_need' && class_exists('AIEO_DMM_Need_Extras_Data')) {
            $row = AIEO_DMM_Need_Extras_Data::get($tax, $tid); $footer = (string) (is_array($row) ? ($row['footer_text'] ?? '') : '');
        } elseif ($tax === 'product_cat') {
            $row = self::get($tax, $tid); $footer = is_array($row) ? (string) ($row['footer_text'] ?? '') : '';
        }
        $term   = get_term($tid, $tax);
        $footer = (string) apply_filters('aieo_taxonomy_archive_footer_text', $footer, $tax, $tid, ($term && !is_wp_error($term)) ? $term : null);
        $footer = trim($footer);
        if ($footer === '') { return ''; }

        
        $img_html = '';
        if ($tax === 'product_cat' && is_array($row)) {
            $mobile_id  = !empty($row['image_mobile_id'])  ? (int) $row['image_mobile_id']  : 0;
            $desktop_id = !empty($row['image_desktop_id']) ? (int) $row['image_desktop_id'] : 0;
            if ((!$mobile_id || !$desktop_id) && $term && !is_wp_error($term) && !empty($term->parent)) {
                $ancestor = $this->resolve_root_ancestor($tax, $tid);
                if ($ancestor) {
                    $anc_row = self::get($tax, (int) $ancestor->term_id);
                    if (is_array($anc_row)) {
                        if (!$mobile_id  && !empty($anc_row['image_mobile_id']))  $mobile_id  = (int) $anc_row['image_mobile_id'];
                        if (!$desktop_id && !empty($anc_row['image_desktop_id'])) $desktop_id = (int) $anc_row['image_desktop_id'];
                    }
                }
            }
            $alt      = ($term && !is_wp_error($term)) ? $term->name : '';
            $img_html = $this->render_responsive_image($mobile_id, $desktop_id, $alt);
        }

        $align     = isset($attributes['align']) ? sanitize_html_class((string) $attributes['align']) : '';
        $align_cls = ('full' === $align) ? ' alignfull' : (('wide' === $align) ? ' alignwide' : '');
        $no_img    = $img_html ? '' : ' aieo-category-footer-hero--no-image';

        $img_kses = array(
            'picture' => array('class' => array()),
            'source'  => array('media' => array(), 'srcset' => array(), 'data-srcset' => array(), 'sizes' => array(), 'type' => array()),
            'img'     => array('src' => array(), 'srcset' => array(), 'sizes' => array(), 'alt' => array(), 'class' => array(), 'width' => array(), 'height' => array(), 'loading' => array(), 'decoding' => array()),
            'figure'  => array('class' => array()),
            'span'    => array('class' => array()),
        );

        ob_start();
        ?>
        <section class="aieo-category-footer-hero<?php echo esc_attr($align_cls . $no_img); ?>" data-taxonomy="<?php echo esc_attr($tax); ?>" data-term-id="<?php echo (int) $tid; ?>">
            <div class="aieo-category-footer-hero__inner">
                <?php if ($img_html): ?>
                    <div class="aieo-category-footer-hero__image"><?php echo wp_kses($img_html, $img_kses); ?></div>
                <?php endif; ?>
                <div class="aieo-category-footer-hero__body"><?php echo wp_kses_post($footer); ?></div>
                <hr class="aieo-category-footer-hero__divider" />
            </div>
        </section>
        <?php
        return (string) ob_get_clean();
    }

    
    private function latest_post_in_term($taxonomy, $term_id) {
        $term_id = (int) $term_id;
        if ($term_id <= 0) { return 0; }
        $ids = get_posts(array(
            'post_type'           => 'post',
            'posts_per_page'      => 1,
            'post_status'         => 'publish',
            'ignore_sticky_posts' => true,
            'no_found_rows'       => true,
            'fields'              => 'ids',
            'tax_query'           => array(array('taxonomy' => (string) $taxonomy, 'field' => 'term_id', 'terms' => $term_id)),
        ));
        return !empty($ids) ? (int) $ids[0] : 0;
    }

    
    public function exclude_blog_hero_post($query) {
        if (is_admin() || !($query instanceof WP_Query) || !$query->is_main_query() || !$query->is_category()) {
            return;
        }
        $term_id = (int) $query->get('cat');
        if ($term_id <= 0) {
            $slug = (string) $query->get('category_name');
            if ($slug !== '') {
                $slug = sanitize_title(basename(untrailingslashit($slug))); 
                $t    = get_term_by('slug', $slug, 'category');
                if ($t && !is_wp_error($t)) { $term_id = (int) $t->term_id; }
            }
        }
        if ($term_id <= 0) { return; }

        $featured = $this->latest_post_in_term('category', $term_id);
        if ($featured <= 0) { return; }

        $exclude = array_map('intval', (array) $query->get('post__not_in'));
        if (!in_array($featured, $exclude, true)) {
            $exclude[] = $featured;
            $query->set('post__not_in', $exclude);
        }
    }

    
    public function render_hero($taxonomy, $term_id, $template_override = '') {
        $row = self::get($taxonomy, $term_id);
        $term = get_term($term_id, $taxonomy);
        if (!$term || is_wp_error($term)) return '';

        $template = $template_override !== '' ? sanitize_key($template_override) : self::get_template($taxonomy, $term_id);
        if ($template === 'hidden') return '';

        $row = is_array($row) ? $row : array();

        $title    = !empty($row['seo_title']) ? (string) $row['seo_title'] : $term->name;
        $headline = !empty($row['headline'])  ? (string) $row['headline']  : '';
        $desc     = (string) $term->description;

        $mobile_id  = !empty($row['image_mobile_id'])  ? (int) $row['image_mobile_id']  : 0;
        $desktop_id = !empty($row['image_desktop_id']) ? (int) $row['image_desktop_id'] : 0;

        
        
        
        
        
        
        
        if ((!$mobile_id || !$desktop_id) && !empty($term->parent)) {
            $ancestor = $this->resolve_root_ancestor($taxonomy, (int) $term->term_id);
            if ($ancestor) {
                $anc_row = self::get($taxonomy, (int) $ancestor->term_id);
                if (is_array($anc_row)) {
                    if (!$mobile_id  && !empty($anc_row['image_mobile_id']))  $mobile_id  = (int) $anc_row['image_mobile_id'];
                    if (!$desktop_id && !empty($anc_row['image_desktop_id'])) $desktop_id = (int) $anc_row['image_desktop_id'];
                }
            }
        }

        $img_html = $this->render_responsive_image($mobile_id, $desktop_id, $title);

        return $this->build_hero_html($title, $headline, $desc, $img_html, $template);
    }

    
    private function build_hero_html($title, $headline, $desc, $img_html, $template = 'default', $extra_classes = array(), $link = '') {
        $link    = (string) $link;
        $classes = array_merge(array(
            'aieo-category-hero',
            'aieo-category-hero--' . sanitize_html_class($template),
        ), (array) $extra_classes);
        if ($link !== '') { $classes[] = 'aieo-category-hero--linked'; }
        
        
        
        
        
        if (!$headline) $classes[] = 'aieo-category-hero--no-headline';
        if (!$desc)     $classes[] = 'aieo-category-hero--no-description';

        ob_start();
        ?>
        <section class="<?php echo esc_attr(implode(' ', $classes)); ?>" data-template="<?php echo esc_attr($template); ?>">
            <header class="aieo-category-hero__title">
                <h1><?php echo esc_html($title); ?></h1>
            </header>
            <div class="aieo-category-hero__image<?php echo $img_html ? '' : ' aieo-category-hero__image--placeholder'; ?>">
                <?php if ($link !== ''): ?><a class="aieo-category-hero__media-link" href="<?php echo esc_url($link); ?>" style="display:block;text-decoration:none !important;color:inherit !important;"><?php endif; ?>
                <?php
                
                
                
                echo wp_kses(
                    (string) ($img_html ?: ''),
                    array(
                        'picture' => array('class' => array()),
                        'source'  => array(
                            'media'       => array(),
                            'srcset'      => array(),
                            'data-srcset' => array(),
                            'sizes'       => array(),
                            'type'        => array(),
                        ),
                        'img'    => array(
                            'src'      => array(),
                            'srcset'   => array(),
                            'sizes'    => array(),
                            'alt'      => array(),
                            'class'    => array(),
                            'width'    => array(),
                            'height'   => array(),
                            'loading'  => array(),
                            'decoding' => array(),
                        ),
                        'figure' => array('class' => array()),
                        'span'   => array('class' => array()),
                    )
                );
                ?>
                <?php if ($link !== ''): ?></a><?php endif; ?>
            </div>
            <?php if ($headline || $desc): ?>
            <div class="aieo-category-hero__body">
                <?php if ($headline): ?>
                    <div class="aieo-category-hero__headline"><?php
                        if ($link !== '') { echo '<a class="aieo-category-hero__text-link" href="' . esc_url($link) . '" style="color:inherit !important;text-decoration:none !important;">' . wp_kses_post($headline) . '</a>'; }
                        else { echo wp_kses_post($headline); }
                    ?></div>
                <?php endif; ?>
                <?php if ($desc): ?>
                    <div class="aieo-category-hero__description"><?php
                        if ($link !== '') { echo '<a class="aieo-category-hero__text-link" href="' . esc_url($link) . '" style="color:inherit !important;text-decoration:none !important;">' . wp_kses_post(wpautop($desc)) . '</a>'; }
                        else { echo wp_kses_post(wpautop($desc)); }
                    ?></div>
                <?php endif; ?>
            </div>
            <?php endif; ?>
            <hr class="aieo-category-hero__divider" />
        </section>
        <?php
        return (string) ob_get_clean();
    }

    
    private function resolve_root_ancestor($taxonomy, $term_id) {
        static $cache = array();
        $key = $taxonomy . ':' . (int) $term_id;
        if (array_key_exists($key, $cache)) return $cache[$key];

        $current = get_term((int) $term_id, $taxonomy);
        while ($current && !is_wp_error($current) && (int) $current->parent > 0) {
            $current = get_term((int) $current->parent, $taxonomy);
        }
        $cache[$key] = ($current && !is_wp_error($current)) ? $current : null;
        return $cache[$key];
    }

    
    private function render_responsive_image($mobile_id, $desktop_id, $alt_fallback) {
        if (!$mobile_id && !$desktop_id) return '';

        $alt = (string) $alt_fallback;
        if ($mobile_id && $desktop_id) {
            $desktop_url = wp_get_attachment_image_url($desktop_id, 'full');
            $desktop_srcset = wp_get_attachment_image_srcset($desktop_id, 'full');
            
            $mobile_img = wp_get_attachment_image(
                $mobile_id, 'large', false,
                array('alt' => $alt, 'class' => 'aieo-category-hero__img', 'loading' => 'lazy', 'decoding' => 'async')
            );
            if ($mobile_img && $desktop_url) {
                $sources  = '<source media="(min-width: 768px)" srcset="' . esc_attr($desktop_url) . '"';
                if ($desktop_srcset) {
                    $sources .= ' data-srcset="' . esc_attr($desktop_srcset) . '"';
                }
                $sources .= ' />';
                return '<picture>' . $sources . $mobile_img . '</picture>';
            }
        }
        
        $only = $desktop_id ?: $mobile_id;
        return wp_get_attachment_image(
            $only, 'large', false,
            array('alt' => $alt, 'class' => 'aieo-category-hero__img', 'loading' => 'lazy', 'decoding' => 'async')
        );
    }

    
    
    

    
    private function resolve_chip_siblings($taxonomy, $term_id) {
        $out = array('parent' => null, 'items' => array());

        $term = get_term((int) $term_id, $taxonomy);
        if (!$term || is_wp_error($term)) return $out;

        
        $kids = get_term_children($term->term_id, $taxonomy);
        $parent_id = (!empty($kids) && !is_wp_error($kids))
            ? (int) $term->term_id
            : ((int) $term->parent ?: (int) $term->term_id);

        $args = (array) apply_filters('aieo_dmm_category_chips_query_args', array(
            'taxonomy'   => $taxonomy,
            'parent'     => $parent_id,
            'hide_empty' => true,
            'orderby'    => 'menu_order',
            'order'      => 'ASC',
        ), $taxonomy, $term_id);
        $siblings = get_terms($args);
        if (is_wp_error($siblings)) $siblings = array();

        
        $excluded = $this->excluded_term_keys_normalised();
        if (!empty($excluded)) {
            $siblings = array_values(array_filter($siblings, static function ($t) use ($taxonomy, $excluded) {
                return !isset($excluded[$taxonomy . ':' . (int) $t->term_id]);
            }));
        }

        $out['items'] = $siblings;

        
        
        
        
        
        if ($parent_id !== (int) $term->term_id) {
            $parent = get_term($parent_id, $taxonomy);
            if ($parent && !is_wp_error($parent)) {
                $out['parent'] = $parent;
            }
        }

        return $out;
    }

    
    private function excluded_term_keys_normalised() {
        $raw = (string) AIEO_DMM_Settings::get('navigator_excluded_terms', '');
        if ($raw === '') return array();
        $out = array();
        foreach (preg_split('/[,\s]+/', $raw) as $tok) {
            $tok = trim($tok);
            if ($tok === '') continue;
            if (strpos($tok, ':') === false) {
                $out[] = 'product_cat:' . (int) $tok;
            } else {
                list($tax, $id) = explode(':', $tok, 2);
                $out[] = sanitize_key($tax) . ':' . (int) $id;
            }
        }
        return array_flip(array_unique($out));
    }

    
    public function shortcode_category_chips($atts = array()) {
        $atts = shortcode_atts(array(
            'taxonomy' => '',
            'term_id'  => 0,
        ), (array) $atts, 'aieo_category_chips');

        if (empty($atts['taxonomy']) || empty($atts['term_id'])) {
            $term = $this->current_term();
            if (!$term) return '';
            $atts['taxonomy'] = $term->taxonomy;
            $atts['term_id']  = (int) $term->term_id;
        }
        return $this->render_chip_strip($atts['taxonomy'], (int) $atts['term_id']);
    }

    public function render_chips_block_callback($attributes) {
        $tax   = isset($attributes['taxonomy']) ? sanitize_key((string) $attributes['taxonomy']) : '';
        $tid   = isset($attributes['termId'])   ? (int) $attributes['termId'] : 0;
        $align = isset($attributes['align'])    ? sanitize_key((string) $attributes['align'])    : '';

        if (!$tax || !$tid) {
            $term = $this->current_term();
            if (!$term) return '';
            $tax = $term->taxonomy;
            $tid = (int) $term->term_id;
        }
        $html = $this->render_chip_strip($tax, $tid);
        if ($html && in_array($align, array('wide', 'full'), true)) {
            $html = preg_replace(
                '/^<nav class="/',
                '<nav class="align' . $align . ' ',
                $html,
                1
            );
        }
        return $html;
    }

    
    
    public static function chips_enabled() {
        return (bool) AIEO_DMM_Settings::get('category_extras_chips_enabled', true);
    }

    
    private function chip_style_vars() {
        $bw  = (int) AIEO_DMM_Settings::get('category_extras_chips_border_width',  1);
        $br  = (int) AIEO_DMM_Settings::get('category_extras_chips_border_radius', 0);
        $bc  = (string) AIEO_DMM_Settings::get('category_extras_chips_border_color', '#F46767');
        $tc  = (string) AIEO_DMM_Settings::get('category_extras_chips_text_color',   '#1a1a1a');
        $py  = (int) AIEO_DMM_Settings::get('category_extras_chips_padding_y', 8);
        $px  = (int) AIEO_DMM_Settings::get('category_extras_chips_padding_x', 22);
        $gy  = (int) AIEO_DMM_Settings::get('category_extras_chips_gap_y', 12);
        $gx  = (int) AIEO_DMM_Settings::get('category_extras_chips_gap_x', 14);
        return implode(';', array(
            '--aieo-chip-border-width:'  . $bw . 'px',
            '--aieo-chip-border-radius:' . $br . 'px',
            '--aieo-chip-border-color:'  . $bc,
            '--aieo-chip-text-color:'    . $tc,
            '--aieo-chip-padding:'       . $py . 'px ' . $px . 'px',
            '--aieo-chip-gap:'           . $gy . 'px ' . $gx . 'px',
        ));
    }

    public function render_chip_strip($taxonomy, $term_id) {
        if (!self::chips_enabled()) return '';
        $resolved = $this->resolve_chip_siblings($taxonomy, $term_id);
        $items    = $resolved['items'];
        $parent   = $resolved['parent'];
        if (empty($items)) return '';

        $current_id = (int) $term_id;
        $slider     = (bool) AIEO_DMM_Settings::get('category_extras_chips_mobile_slider', false);
        $classes    = 'aieo-category-chips' . ($slider ? ' aieo-category-chips--mobile-slider' : '');
        $style_attr = $this->chip_style_vars();
        ob_start();
        ?>
        <nav class="<?php echo esc_attr($classes); ?>" style="<?php echo esc_attr($style_attr); ?>" aria-label="<?php esc_attr_e('Subcategories', 'ai-eshop-optimizer'); ?>">
            <div class="aieo-category-chips__inner">
                <?php if ($parent): ?>
                    <?php
                    $is_active_parent = ((int) $parent->term_id === $current_id);
                    $cls_p = 'aieo-category-chip aieo-category-chip--parent' . ($is_active_parent ? ' is-active' : '');
                    ?>
                    <a class="<?php echo esc_attr($cls_p); ?>"
                       href="<?php echo esc_url(get_term_link($parent)); ?>"
                       <?php if ($is_active_parent): ?>aria-current="page"<?php endif; ?>>
                        <?php echo esc_html($parent->name); ?>
                    </a>
                <?php endif; ?>

                <?php foreach ($items as $t): ?>
                    <?php
                    $is_active = ((int) $t->term_id === $current_id);
                    $classes = 'aieo-category-chip' . ($is_active ? ' is-active' : '');
                    ?>
                    <a class="<?php echo esc_attr($classes); ?>"
                       href="<?php echo esc_url(get_term_link($t)); ?>"
                       <?php if ($is_active): ?>aria-current="page"<?php endif; ?>>
                        <?php echo esc_html($t->name); ?>
                    </a>
                <?php endforeach; ?>
            </div>
        </nav>
        <?php
        return (string) ob_get_clean();
    }

    
    public function enqueue_frontend_assets() {
        $should_load = false;
        $term = $this->current_term();
        if ($term) $should_load = true;
        if (!$should_load && is_singular()) {
            global $post;
            if ($post && (
                has_shortcode((string) $post->post_content, 'aieo_category_hero') ||
                has_shortcode((string) $post->post_content, 'aieo_category_chips')
            )) {
                $should_load = true;
            }
        }
        $should_load = (bool) apply_filters('aieo_dmm_category_extras_enqueue_frontend', $should_load);
        if (!$should_load) return;

        
        
        
        $css_path = defined('AIEO_DIR') ? AIEO_DIR . 'assets/css/aieo-dmm-category-extras.css' : '';
        $css_ver  = ($css_path && file_exists($css_path))
            ? (string) filemtime($css_path)
            : (defined('AIEO_VERSION') ? AIEO_VERSION : '1.0');
        wp_enqueue_style(
            'aieo-dmm-category-extras',
            AIEO_URI . 'assets/css/aieo-dmm-category-extras.css',
            array(),
            $css_ver
        );
    }

    
    
    

    public function render_subtab() {
        global $wpdb;
        $tbl   = self::table();
        $exists = (string) $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tbl)) === $tbl;
        $count  = $exists ? (int) $wpdb->get_var("SELECT COUNT(*) FROM {$tbl}") : 0;
        $migrated_at = (int) get_option('aieo_dmm_category_extras_migrated_at', 0);
        $chips_on    = self::chips_enabled();
        
        $cs = array(
            'bw'  => (int)    AIEO_DMM_Settings::get('category_extras_chips_border_width',  1),
            'br'  => (int)    AIEO_DMM_Settings::get('category_extras_chips_border_radius', 0),
            'bc'  => (string) AIEO_DMM_Settings::get('category_extras_chips_border_color', '#F46767'),
            'tc'  => (string) AIEO_DMM_Settings::get('category_extras_chips_text_color',   '#1a1a1a'),
            'py'  => (int)    AIEO_DMM_Settings::get('category_extras_chips_padding_y', 8),
            'px'  => (int)    AIEO_DMM_Settings::get('category_extras_chips_padding_x', 22),
            'gy'  => (int)    AIEO_DMM_Settings::get('category_extras_chips_gap_y', 12),
            'gx'  => (int)    AIEO_DMM_Settings::get('category_extras_chips_gap_x', 14),
            'sl'  => (bool)   AIEO_DMM_Settings::get('category_extras_chips_mobile_slider', false),
        );
        $ns = array(
            'bw' => (int)    AIEO_DMM_Settings::get('navigator_border_width',  1),
            'br' => (int)    AIEO_DMM_Settings::get('navigator_border_radius', 12),
            'bc' => (string) AIEO_DMM_Settings::get('navigator_border_color', '#e5e7eb'),
        );
        ?>
        <div class="aieo-dmm-subtab aieo-dmm-category-extras">
            <h2><?php esc_html_e('Category Extras', 'ai-eshop-optimizer'); ?></h2>
            <p class="description">
                <?php esc_html_e('Custom AIEO storage for advanced category fields (headline, hero images for mobile + desktop, sidebar/footer text, landing pages). Replaces the term-meta-based "Advanced Categories" feature owned by om-category-essentials.', 'ai-eshop-optimizer'); ?>
            </p>

            <table class="form-table" role="presentation">
                <tbody>
                    <tr>
                        <th scope="row"><?php esc_html_e('Storage', 'ai-eshop-optimizer'); ?></th>
                        <td>
                            <?php if ($exists): ?>
                                <code>wp_aieo_dmm_category_extras</code>
                                <span style="color:#46b450;">✓</span>
                                — <?php
                                
                                printf(esc_html__('%d row(s)', 'ai-eshop-optimizer'), (int) $count);
                                ?>
                            <?php else: ?>
                                <span style="color:#dc3232;">✗ <?php esc_html_e('Table missing — re-activate the plugin to create it.', 'ai-eshop-optimizer'); ?></span>
                            <?php endif; ?>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><?php esc_html_e('Last migration', 'ai-eshop-optimizer'); ?></th>
                        <td>
                            <?php if ($migrated_at): ?>
                                <code><?php echo esc_html(human_time_diff($migrated_at) . ' ' . __('ago', 'ai-eshop-optimizer')); ?></code>
                            <?php else: ?>
                                <em><?php esc_html_e('Never', 'ai-eshop-optimizer'); ?></em>
                            <?php endif; ?>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><?php esc_html_e('Run migration', 'ai-eshop-optimizer'); ?></th>
                        <td>
                            <button type="button" class="button button-primary" id="aieo-ce-migrate">
                                <?php esc_html_e('Migrate from om-category-essentials', 'ai-eshop-optimizer'); ?>
                            </button>
                            <span class="aieo-ce-migrate-status" style="margin-left:8px;"></span>
                            <p class="description">
                                <?php esc_html_e('Idempotent — re-running overwrites with the latest source values. Sources:', 'ai-eshop-optimizer'); ?>
                            </p>
                            <ul class="description" style="margin-left:18px;list-style:disc;">
                                <li><?php esc_html_e('Legacy om-category-essentials termmeta — seo_title, background → desktop image, short_description → headline, sidebar/footer text, columns, disable_nested, landing pages.', 'ai-eshop-optimizer'); ?></li>
                                <li><?php esc_html_e('Pods scalar fields (wp_pods_product_cat) — pro_only, reference_quantity, synonyms, season_start / season_end / season_midpoint.', 'ai-eshop-optimizer'); ?></li>
                                <li><?php esc_html_e('Pods relationships (wp_podsrel) — related_brand, related_posts, related_campaigns. Manual weight ordering preserved.', 'ai-eshop-optimizer'); ?></li>
                            </ul>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><?php esc_html_e('Lift H3 → headline', 'ai-eshop-optimizer'); ?></th>
                        <td>
                            <?php $h3_migrated_at = (int) get_option('aieo_dmm_category_extras_h3_migrated_at', 0); ?>
                            <?php if ($h3_migrated_at): ?>
                                <p style="margin:0;">
                                    <span style="color:#46b450;">✓</span>
                                    <?php
                                    printf(
                                        
                                        esc_html__('Already run on this site %s ago. The button is hidden by design — re-running would touch term descriptions again.', 'ai-eshop-optimizer'),
                                        esc_html(human_time_diff($h3_migrated_at))
                                    );
                                    ?>
                                </p>
                                <p class="description">
                                    <?php esc_html_e('To force a re-run (after deliberately reverting a description with a fresh leading h3) clear the `aieo_dmm_category_extras_h3_migrated_at` option via WP-CLI:', 'ai-eshop-optimizer'); ?>
                                    <code>wp option delete aieo_dmm_category_extras_h3_migrated_at</code>
                                </p>
                            <?php else: ?>
                                <button type="button" class="button" id="aieo-ce-h3-migrate">
                                    <?php esc_html_e('Move first <h3> from description into the headline', 'ai-eshop-optimizer'); ?>
                                </button>
                                <span class="aieo-ce-h3-migrate-status" style="margin-left:8px;"></span>
                                <p class="description">
                                    <?php esc_html_e('Many legacy descriptions open with `<h3><strong>…</strong></h3>` as the marketing headline. This walks every term in the managed taxonomies (product_cat, product_brand, pa_need), lifts the inner HTML of the first h3 into the `headline` column, and strips the heading from the description so the hero doesn\'t render the same line twice. One-shot — once you click it, the button hides itself.', 'ai-eshop-optimizer'); ?>
                                </p>
                            <?php endif; ?>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><?php esc_html_e('Subcategory chips', 'ai-eshop-optimizer'); ?></th>
                        <td>
                            <label>
                                <input type="checkbox" id="aieo-ce-chips-enabled" <?php checked($chips_on); ?> />
                                <?php esc_html_e('Render the subcategory chip strip on category archive pages', 'ai-eshop-optimizer'); ?>
                            </label>
                            <span class="aieo-ce-chips-status" style="margin-left:8px;"></span>
                            <p class="description">
                                <strong><?php esc_html_e('What this does:', 'ai-eshop-optimizer'); ?></strong>
                                <?php esc_html_e('Below the hero on every product category archive, renders a horizontal strip of pill-style chips so shoppers can move between sibling / child categories without backtracking.', 'ai-eshop-optimizer'); ?>
                            </p>
                            <ul class="description" style="margin-left:18px;list-style:disc;">
                                <li><?php esc_html_e('On a top-level or mid-level category — shows that term\'s direct children (drill-down).', 'ai-eshop-optimizer'); ?></li>
                                <li><?php esc_html_e('On a leaf category (no children) — shows that term\'s siblings, with a "‹ Parent" back-link chip first, so navigation never dead-ends.', 'ai-eshop-optimizer'); ?></li>
                                <li><?php esc_html_e('The currently-viewed term reads as a filled accent pill and is click-disabled (aria-current="page").', 'ai-eshop-optimizer'); ?></li>
                                <li><?php esc_html_e('Empty terms (zero products) are filtered out. Terms listed in the Navigator → Hidden terms textarea are also hidden here — single source of truth.', 'ai-eshop-optimizer'); ?></li>
                            </ul>
                            <p class="description">
                                <?php esc_html_e('Disable this if your theme provides its own subcategory navigation, or if you want a chip-free archive layout. The block / shortcode then returns empty and the archive shows just the hero + product grid.', 'ai-eshop-optimizer'); ?>
                            </p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><?php esc_html_e('Chip border', 'ai-eshop-optimizer'); ?></th>
                        <td>
                            <label style="margin-right:14px;">
                                <?php esc_html_e('Width (px):', 'ai-eshop-optimizer'); ?>
                                <input type="number" min="0" max="10" step="1" id="aieo-ce-chips-bw" value="<?php echo (int) $cs['bw']; ?>" class="small-text" style="width:60px;">
                            </label>
                            <label style="margin-right:14px;">
                                <?php esc_html_e('Radius (px):', 'ai-eshop-optimizer'); ?>
                                <input type="number" min="0" max="60" step="1" id="aieo-ce-chips-br" value="<?php echo (int) $cs['br']; ?>" class="small-text" style="width:60px;">
                            </label>
                            <label style="margin-right:14px;">
                                <?php esc_html_e('Border colour:', 'ai-eshop-optimizer'); ?>
                                <input type="color" id="aieo-ce-chips-bc" value="<?php echo esc_attr($cs['bc']); ?>">
                            </label>
                            <label>
                                <?php esc_html_e('Text colour:', 'ai-eshop-optimizer'); ?>
                                <input type="color" id="aieo-ce-chips-tc" value="<?php echo esc_attr($cs['tc']); ?>">
                            </label>
                            <p class="description">
                                <?php esc_html_e('Border colour is also used as the active / hover fill (with white text on top). Default border 1px, square corners, accent colour.', 'ai-eshop-optimizer'); ?>
                            </p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><?php esc_html_e('Chip spacing', 'ai-eshop-optimizer'); ?></th>
                        <td>
                            <label style="margin-right:14px;">
                                <?php esc_html_e('Padding Y (px):', 'ai-eshop-optimizer'); ?>
                                <input type="number" min="0" max="64" step="1" id="aieo-ce-chips-py" value="<?php echo (int) $cs['py']; ?>" class="small-text" style="width:60px;">
                            </label>
                            <label style="margin-right:14px;">
                                <?php esc_html_e('Padding X (px):', 'ai-eshop-optimizer'); ?>
                                <input type="number" min="0" max="96" step="1" id="aieo-ce-chips-px" value="<?php echo (int) $cs['px']; ?>" class="small-text" style="width:60px;">
                            </label>
                            <label style="margin-right:14px;">
                                <?php esc_html_e('Gap row (px):', 'ai-eshop-optimizer'); ?>
                                <input type="number" min="0" max="64" step="1" id="aieo-ce-chips-gy" value="<?php echo (int) $cs['gy']; ?>" class="small-text" style="width:60px;">
                            </label>
                            <label>
                                <?php esc_html_e('Gap column (px):', 'ai-eshop-optimizer'); ?>
                                <input type="number" min="0" max="64" step="1" id="aieo-ce-chips-gx" value="<?php echo (int) $cs['gx']; ?>" class="small-text" style="width:60px;">
                            </label>
                            <p class="description">
                                <?php esc_html_e('Padding controls how thick each chip looks; gap controls breathing room between chips (vertical / horizontal).', 'ai-eshop-optimizer'); ?>
                            </p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><?php esc_html_e('Mobile layout', 'ai-eshop-optimizer'); ?></th>
                        <td>
                            <label>
                                <input type="checkbox" id="aieo-ce-chips-slider" <?php checked($cs['sl']); ?>>
                                <?php esc_html_e('On viewports ≤767px, render chips as a single-row horizontal slider instead of wrapping to multiple rows', 'ai-eshop-optimizer'); ?>
                            </label>
                            <p class="description">
                                <?php esc_html_e('Pure-CSS scroller — touch swipe + a slim styled scrollbar. Matches the legacy sombrecosmetics behaviour. Off by default; turn on if your archive lists many subcategories and the wrapped grid eats too much vertical space on phones.', 'ai-eshop-optimizer'); ?>
                            </p>
                        </td>
                    </tr>
                    <tr>
                        <th scope="row"><?php esc_html_e('Navigator palette border', 'ai-eshop-optimizer'); ?></th>
                        <td>
                            <label style="margin-right:14px;">
                                <?php esc_html_e('Width (px):', 'ai-eshop-optimizer'); ?>
                                <input type="number" min="0" max="8" step="1" id="aieo-nav-bw" value="<?php echo (int) $ns['bw']; ?>" class="small-text" style="width:60px;">
                            </label>
                            <label style="margin-right:14px;">
                                <?php esc_html_e('Radius (px):', 'ai-eshop-optimizer'); ?>
                                <input type="number" min="0" max="40" step="1" id="aieo-nav-br" value="<?php echo (int) $ns['br']; ?>" class="small-text" style="width:60px;">
                            </label>
                            <label>
                                <?php esc_html_e('Border colour:', 'ai-eshop-optimizer'); ?>
                                <input type="color" id="aieo-nav-bc" value="<?php echo esc_attr($ns['bc']); ?>">
                            </label>
                            <p class="description">
                                <?php esc_html_e('Tunes the Ctrl+K palette modal\'s outer border. Applied via CSS variables on the palette root, so changes take effect on the next palette open without rebuilding the navigator JSON.', 'ai-eshop-optimizer'); ?>
                            </p>
                        </td>
                    </tr>
                </tbody>
            </table>

            <script>
            (function () {
                var btn         = document.getElementById('aieo-ce-migrate');
                var statusEl    = document.querySelector('.aieo-ce-migrate-status');
                var chipsToggle = document.getElementById('aieo-ce-chips-enabled');
                var chipsStatus = document.querySelector('.aieo-ce-chips-status');

                // Render nonce directly via PHP — wp_localize_script timing
                // races inline scripts in admin sub-tab body.
                var nonce   = <?php echo wp_json_encode( wp_create_nonce( AIEO_DMM::NONCE_ACTION ) ); ?>;
                var ajaxurl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>;

                if (btn) {
                    btn.addEventListener('click', function () {
                        btn.disabled = true;
                        statusEl.textContent = '<?php echo esc_js(__('Migrating…', 'ai-eshop-optimizer')); ?>';
                        var fd = new FormData();
                        fd.set('action', 'aieo_dmm_category_extras_migrate');
                        fd.set('nonce', nonce);
                        fetch(ajaxurl, { method: 'POST', body: fd, credentials: 'same-origin' })
                            .then(function (r) { return r.json(); })
                            .then(function (j) {
                                if (j && j.success) {
                                    var s = j.data.stats || {};
                                    statusEl.textContent = '<?php echo esc_js(__('Done — migrated', 'ai-eshop-optimizer')); ?> ' + s.migrated + ' / ' + s.processed;
                                    setTimeout(function () { window.location.reload(); }, 800);
                                } else {
                                    statusEl.textContent = '<?php echo esc_js(__('Failed.', 'ai-eshop-optimizer')); ?>';
                                }
                            })
                            .catch(function () { statusEl.textContent = '<?php echo esc_js(__('Failed (network)', 'ai-eshop-optimizer')); ?>'; })
                            .finally(function () { btn.disabled = false; });
                    });
                }

                var h3Btn       = document.getElementById('aieo-ce-h3-migrate');
                var h3StatusEl  = document.querySelector('.aieo-ce-h3-migrate-status');
                if (h3Btn) {
                    h3Btn.addEventListener('click', function () {
                        if (!window.confirm('<?php echo esc_js(__('This will modify category / brand / need descriptions in the database. Continue?', 'ai-eshop-optimizer')); ?>')) return;
                        h3Btn.disabled = true;
                        h3StatusEl.textContent = '<?php echo esc_js(__('Lifting headlines…', 'ai-eshop-optimizer')); ?>';
                        var fd = new FormData();
                        fd.set('action', 'aieo_dmm_category_extras_h3_migrate');
                        fd.set('nonce', nonce);
                        fetch(ajaxurl, { method: 'POST', body: fd, credentials: 'same-origin' })
                            .then(function (r) { return r.json(); })
                            .then(function (j) {
                                if (j && j.success) {
                                    var s = j.data.stats || {};
                                    h3StatusEl.textContent = '<?php echo esc_js(__('Done — headlines lifted', 'ai-eshop-optimizer')); ?>: ' + s.headlines + ' / ' + s.processed +
                                        ' (<?php echo esc_js(__('skipped', 'ai-eshop-optimizer')); ?>: ' + s.skipped +
                                        ', <?php echo esc_js(__('errors', 'ai-eshop-optimizer')); ?>: ' + s.errors + ')';
                                } else {
                                    h3StatusEl.textContent = '<?php echo esc_js(__('Failed.', 'ai-eshop-optimizer')); ?>';
                                }
                            })
                            .catch(function () { h3StatusEl.textContent = '<?php echo esc_js(__('Failed (network)', 'ai-eshop-optimizer')); ?>'; })
                            .finally(function () { h3Btn.disabled = false; });
                    });
                }

                // Generic settings save — gathers the value of every
                // tunable field every time, so any change posts a complete
                // snapshot. Server-side sanitize() clamps and falls back.
                function buildSettingsPayload(extra) {
                    var fd = new FormData();
                    fd.set('action', 'aieo_dmm_category_extras_save_settings');
                    fd.set('nonce', nonce);
                    if (chipsToggle && chipsToggle.checked) fd.set('chips_enabled', '1');
                    var sliderEl = document.getElementById('aieo-ce-chips-slider');
                    if (sliderEl && sliderEl.checked) fd.set('chips_mobile_slider', '1');
                    var fields = {
                        'aieo-ce-chips-bw':  'chips_border_width',
                        'aieo-ce-chips-br':  'chips_border_radius',
                        'aieo-ce-chips-bc':  'chips_border_color',
                        'aieo-ce-chips-tc':  'chips_text_color',
                        'aieo-ce-chips-py':  'chips_padding_y',
                        'aieo-ce-chips-px':  'chips_padding_x',
                        'aieo-ce-chips-gy':  'chips_gap_y',
                        'aieo-ce-chips-gx':  'chips_gap_x',
                        'aieo-nav-bw':       'nav_border_width',
                        'aieo-nav-br':       'nav_border_radius',
                        'aieo-nav-bc':       'nav_border_color'
                    };
                    Object.keys(fields).forEach(function (id) {
                        var el = document.getElementById(id);
                        if (el && el.value !== '') fd.set(fields[id], el.value);
                    });
                    if (extra) Object.keys(extra).forEach(function (k) { fd.set(k, extra[k]); });
                    return fd;
                }

                function flashStatus(el, msg) {
                    if (!el) return;
                    el.textContent = msg;
                    setTimeout(function () { el.textContent = ''; }, 1500);
                }

                function saveSettings(statusEl) {
                    if (statusEl) statusEl.textContent = '<?php echo esc_js(__('Saving…', 'ai-eshop-optimizer')); ?>';
                    return fetch(ajaxurl, { method: 'POST', body: buildSettingsPayload(), credentials: 'same-origin' })
                        .then(function (r) { return r.json().catch(function () { return null; }); })
                        .then(function (j) {
                            if (!j || !j.success) {
                                if (statusEl) statusEl.textContent = '<?php echo esc_js(__('Save failed.', 'ai-eshop-optimizer')); ?>';
                                return false;
                            }
                            flashStatus(statusEl, '<?php echo esc_js(__('Saved.', 'ai-eshop-optimizer')); ?>');
                            return true;
                        })
                        .catch(function () {
                            if (statusEl) statusEl.textContent = '<?php echo esc_js(__('Save failed (network).', 'ai-eshop-optimizer')); ?>';
                            return false;
                        });
                }

                // Toggle (chip strip on/off) — bind directly to the chip-toggle
                // element so its specific status pill flashes save state.
                if (chipsToggle) {
                    chipsToggle.addEventListener('change', function () {
                        var prev = !chipsToggle.checked;
                        chipsToggle.disabled = true;
                        saveSettings(chipsStatus).then(function (ok) {
                            if (!ok) chipsToggle.checked = prev;
                        }).finally(function () { chipsToggle.disabled = false; });
                    });
                }

                // All other tunable fields — debounced save on change/input
                // so dragging a colour picker or tweaking a number doesn't
                // post on every key.
                var saveTimer = null;
                function scheduleSave() {
                    clearTimeout(saveTimer);
                    saveTimer = setTimeout(function () { saveSettings(chipsStatus); }, 350);
                }
                ['aieo-ce-chips-slider',
                 'aieo-ce-chips-bw','aieo-ce-chips-br','aieo-ce-chips-bc','aieo-ce-chips-tc',
                 'aieo-ce-chips-py','aieo-ce-chips-px','aieo-ce-chips-gy','aieo-ce-chips-gx',
                 'aieo-nav-bw','aieo-nav-br','aieo-nav-bc'
                ].forEach(function (id) {
                    var el = document.getElementById(id);
                    if (!el) return;
                    el.addEventListener('change', scheduleSave);
                    if (el.type === 'number' || el.type === 'color') {
                        el.addEventListener('input', scheduleSave);
                    }
                });
            })();
            </script>
        </div>
        <?php
    }
}
