<?php

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

class AIEO_DMM_Marketplace_Feeds extends AIEO_DMM_Module_Base {

    public function get_id() { return 'marketplace_feeds'; }
    public function get_label() { return __('Marketplace Feeds', 'ai-eshop-optimizer'); }
    public function get_priority() { return 8; }
    public function get_group() { return 'import_export'; }

    const CRON_HOOK = 'aieo_dmm_generate_feed';

    public function register_hooks() {
        add_action('wp_ajax_aieo_dmm_feed_save', array($this, 'ajax_save'));
        add_action('wp_ajax_aieo_dmm_feed_delete', array($this, 'ajax_delete'));
        add_action('wp_ajax_aieo_dmm_feed_run_now', array($this, 'ajax_run_now'));
        add_action('wp_ajax_aieo_dmm_feed_get', array($this, 'ajax_get'));
        add_action('wp_ajax_aieo_dmm_feed_save_availability', array($this, 'ajax_save_availability'));
        add_action('wp_ajax_aieo_dmm_search_categories', array($this, 'ajax_search_categories'));
        
        add_action('wp_ajax_aieo_dmm_feed_search_products', array($this, 'ajax_feed_search_products'));
        add_action('wp_ajax_aieo_dmm_feed_get_variations', array($this, 'ajax_feed_get_variations'));
        add_action(self::CRON_HOOK, array($this, 'generate_feed_by_id'), 10, 1);

        
        
        
        
        
        add_action('plugins_loaded', array($this, 'maybe_schedule_feeds'), 26);
    }

    
    public function maybe_schedule_feeds() {
        global $wpdb;
        $table = $wpdb->prefix . 'aieo_dmm_feed_configs';
        if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) !== $table) {
            return; 
        }
        
        $rows = $wpdb->get_results(
            "SELECT id, schedule FROM {$table}
             WHERE enabled = 1 AND schedule IN ('hourly','twicedaily','daily')",
            ARRAY_A
        );
        if (empty($rows)) { return; }
        foreach ($rows as $r) {
            $id       = (int) $r['id'];
            $schedule = (string) $r['schedule'];
            if (!wp_next_scheduled(self::CRON_HOOK, array($id))) {
                wp_schedule_event(time() + 600, $schedule, self::CRON_HOOK, array($id));
            }
        }
    }

    
    public function ajax_feed_search_products() {
        AIEO_DMM::verify_ajax_request();
        $q = isset($_GET['q']) ? sanitize_text_field(wp_unslash($_GET['q'])) : '';
        wp_send_json(array('results' => $this->search_products_raw($q, 30)));
    }

    
    public function ajax_feed_get_variations() {
        AIEO_DMM::verify_ajax_request();
        $parent_id = isset($_GET['parent_id']) ? absint(wp_unslash($_GET['parent_id'])) : 0;
        $out = array();
        if ($parent_id && function_exists('wc_get_product')) {
            $parent = wc_get_product($parent_id);
            if ($parent && $parent->is_type('variable')) {
                foreach ($parent->get_children() as $vid) {
                    $v = wc_get_product($vid);
                    if (!$v) { continue; }
                    $out[] = array(
                        'id'     => (int) $vid,
                        'text'   => self::variation_label($v),
                        'status' => $v->get_status(),
                    );
                }
            }
        }
        wp_send_json(array('results' => $out));
    }

    
    private static function variation_label($v) {
        $attrs = function_exists('wc_get_formatted_variation') ? (string) wc_get_formatted_variation($v, true) : '';
        $label = $attrs !== '' ? $attrs : ('#' . (int) $v->get_id());
        if ($v->get_sku()) { $label .= ' [' . $v->get_sku() . ']'; }
        return $label;
    }

    
    private function search_products_raw($q, $limit) {
        if (!function_exists('wc_get_product')) { return array(); }
        global $wpdb;
        $limit = max(1, (int) $limit);
        $q     = trim((string) $q);

        if ($q === '') {
            
            $ids = $wpdb->get_col($wpdb->prepare(
                "SELECT p.ID FROM {$wpdb->posts} p
                 WHERE p.post_type = 'product' AND p.post_status IN ('publish','draft','private')
                 GROUP BY p.ID
                 ORDER BY (p.post_status='publish') DESC, p.post_title ASC
                 LIMIT %d",
                $limit
            ));
        } else {
            $like = '%' . $wpdb->esc_like($q) . '%';
            
            
            
            $brand_tt = $wpdb->get_col($wpdb->prepare(
                "SELECT tt.term_taxonomy_id
                 FROM {$wpdb->term_taxonomy} tt
                 INNER JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
                 WHERE tt.taxonomy = 'product_brand' AND t.name LIKE %s
                 LIMIT 20",
                $like
            ));
            $brand_sql = '';
            $params    = array($like, $like);
            if (!empty($brand_tt)) {
                $ph        = implode(',', array_fill(0, count($brand_tt), '%d'));
                $brand_sql = " OR p.ID IN (SELECT tr.object_id FROM {$wpdb->term_relationships} tr WHERE tr.term_taxonomy_id IN ($ph)) ";
                $params    = array_merge($params, array_map('intval', $brand_tt));
            }
            $params[] = $limit;
            
            
            
            $ids = $wpdb->get_col($wpdb->prepare(
                "SELECT p.ID
                 FROM {$wpdb->posts} p
                 LEFT JOIN {$wpdb->postmeta} sku ON sku.post_id = p.ID AND sku.meta_key = '_sku'
                 WHERE p.post_type = 'product' AND p.post_status IN ('publish','draft','private')
                   AND ( p.post_title LIKE %s OR sku.meta_value LIKE %s {$brand_sql} )
                 GROUP BY p.ID
                 ORDER BY (p.post_status='publish') DESC, p.post_title ASC
                 LIMIT %d",
                $params
            ));
        }

        $results = array();
        foreach ((array) $ids as $pid) {
            $p = wc_get_product((int) $pid);
            if (!$p) { continue; }
            $is_variable = $p->is_type('variable');
            $results[] = array(
                'id'           => (int) $pid,
                'text'         => $p->get_name() . ($p->get_sku() ? ' [' . $p->get_sku() . ']' : ''),
                'name'         => $p->get_name(), 
                'type'         => $is_variable ? 'variable' : 'simple',
                'status'       => $p->get_status(),
                'n_variations' => $is_variable ? count($p->get_children()) : 0,
            );
        }
        return $results;
    }

    
    public function ajax_get() {
        AIEO_DMM::verify_ajax_request();

        $id = isset($_GET['id']) ? absint(wp_unslash($_GET['id'])) : 0;
        if ($id <= 0) {
            wp_send_json_error(array('message' => __('Invalid id.', 'ai-eshop-optimizer')));
        }

        global $wpdb;
        $table = $wpdb->prefix . 'aieo_dmm_feed_configs';
        if (!aieo_dmm_table_exists($table)) {
            wp_send_json_error(array('message' => __('Table missing.', 'ai-eshop-optimizer')));
        }

        
        $row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $id), ARRAY_A);
        if (!is_array($row)) {
            wp_send_json_error(array('message' => __('Feed not found.', 'ai-eshop-optimizer')));
        }

        foreach (array(
            'include_ids', 'include_brand_ids', 'exclude_ids', 'exclude_brand_ids',
            'include_category_ids', 'exclude_category_ids', 'field_map',
        ) as $jcol) {
            $row[$jcol] = isset($row[$jcol]) ? json_decode($row[$jcol], true) : null;
            if (!is_array($row[$jcol])) {
                $row[$jcol] = array();
            }
        }

        
        
        $row['include_brand_slugs'] = self::ids_to_brand_slugs($row['include_brand_ids']);
        $row['exclude_brand_slugs'] = self::ids_to_brand_slugs($row['exclude_brand_ids']);

        
        
        
        
        $row['include_products'] = self::ids_to_product_pairs($row['include_ids']);
        $row['exclude_products'] = self::ids_to_product_pairs($row['exclude_ids']);
        $row['include_brands']   = self::ids_to_brand_pairs($row['include_brand_ids']);
        $row['exclude_brands']   = self::ids_to_brand_pairs($row['exclude_brand_ids']);

        wp_send_json_success(array('feed' => $row));
    }

    
    private static function ids_to_product_pairs(array $list): array {
        $out = array();
        foreach ($list as $entry) {
            $pid = (int) $entry;
            if ($pid <= 0) { continue; }

            $post = get_post($pid);
            if (!$post || ($post->post_type !== 'product' && $post->post_type !== 'product_variation')) {
                $out[] = array('id' => $pid, 'text' => '#' . $pid, 'type' => 'missing', 'status' => 'missing');
                continue;
            }

            if ($post->post_type === 'product_variation') {
                $parent_id = (int) $post->post_parent;
                $v = function_exists('wc_get_product') ? wc_get_product($pid) : null;
                $label = $v ? self::variation_label($v) : ('#' . $pid);
                $pname = $parent_id ? (string) get_the_title($parent_id) : '';
                $text  = ($pname !== '' ? $pname : ('#' . $parent_id)) . ' — ' . $label;
                
                $status = $parent_id ? (string) get_post_status($parent_id) : $post->post_status;
                $out[] = array('id' => $pid, 'text' => $text, 'type' => 'variation', 'status' => $status, 'parent_id' => $parent_id);
                continue;
            }

            $p = function_exists('wc_get_product') ? wc_get_product($pid) : null;
            $is_variable = $p && $p->is_type('variable');
            $title = (string) get_the_title($pid);
            $sku   = $p ? (string) $p->get_sku() : '';
            $text  = ($title !== '' ? $title : ('#' . $pid)) . ($sku !== '' ? ' [' . $sku . ']' : '');
            $out[] = array(
                'id'     => $pid,
                'text'   => $text,
                'type'   => $is_variable ? 'variable' : 'simple',
                'status' => $post->post_status,
            );
        }
        return $out;
    }

    
    private static function ids_to_brand_pairs(array $list): array {
        $out = array();
        foreach ($list as $entry) {
            $tid = (int) $entry;
            if ($tid <= 0) { continue; }
            $term = get_term($tid, 'product_brand');
            if ($term && !is_wp_error($term)) {
                $out[] = array('id' => $tid, 'text' => $term->name . ' (' . (int) $term->count . ')');
            }
        }
        return $out;
    }

    private static function ids_to_brand_slugs(array $list): array {
        $out = array();
        foreach ($list as $entry) {
            if (is_int($entry) || (is_string($entry) && ctype_digit((string) $entry))) {
                $term = get_term((int) $entry, 'product_brand');
                if ($term && !is_wp_error($term)) {
                    $out[] = $term->slug;
                    continue;
                }
            }
            $out[] = (string) $entry;
        }
        return $out;
    }

    
    public function ajax_search_categories() {
        AIEO_DMM::verify_ajax_request();

        $q = isset($_GET['q']) ? sanitize_text_field(wp_unslash($_GET['q'])) : '';
        $args = array(
            'taxonomy'   => 'product_cat',
            'hide_empty' => false,
            'number'     => 30,
        );
        if ($q !== '') {
            $args['name__like'] = $q;
        }
        $terms = get_terms($args);
        $results = array();
        if (is_array($terms)) {
            foreach ($terms as $term) {
                
                
                $label = $term->name;
                $ancestors = get_ancestors((int) $term->term_id, 'product_cat');
                if (!empty($ancestors)) {
                    $chain = array();
                    foreach (array_reverse($ancestors) as $anc_id) {
                        $anc = get_term($anc_id, 'product_cat');
                        if ($anc && !is_wp_error($anc)) {
                            $chain[] = $anc->name;
                        }
                    }
                    $chain[] = $term->name;
                    $label = implode(' › ', $chain);
                }
                $results[] = array(
                    'id'   => (int) $term->term_id,
                    'text' => $label . ' (' . (int) $term->count . ')',
                );
            }
        }
        wp_send_json(array('results' => $results));
    }

    public function render_subtab() {
        $this->render_template('marketplace-feeds.php', array(
            'feeds' => $this->fetch_feeds(),
            'templates' => $this->get_templates(),
            'availability_instock' => (string) get_option('aieo_feed_availability_instock', 'Παράδοση σε 1 - 3 ημέρες'),
            'availability_oos'     => (string) get_option('aieo_feed_availability_oos', 'Μη διαθέσιμο'),
            'shipping_lead_time'   => (string) get_option('aieo_feed_default_lead_time', '0'),
        ));
    }

    
    public function ajax_save_availability() {
        AIEO_DMM::verify_ajax_request();
        $instock = isset($_POST['availability_instock'])
            ? sanitize_text_field((string) wp_unslash($_POST['availability_instock']))
            : '';
        $oos = isset($_POST['availability_oos'])
            ? sanitize_text_field((string) wp_unslash($_POST['availability_oos']))
            : '';
        $lead = isset($_POST['shipping_lead_time'])
            ? sanitize_text_field((string) wp_unslash($_POST['shipping_lead_time']))
            : '0';
        update_option('aieo_feed_availability_instock', $instock, false);
        update_option('aieo_feed_availability_oos', $oos, false);
        update_option('aieo_feed_default_lead_time', $lead, false);

        
        if (isset($_POST['etsy_who_made'])) {
            $who = sanitize_key((string) wp_unslash($_POST['etsy_who_made']));
            update_option('aieo_feed_etsy_who_made', in_array($who, array('i_did', 'someone_else', 'collective'), true) ? $who : 'i_did', false);
        }
        if (isset($_POST['etsy_when_made'])) {
            update_option('aieo_feed_etsy_when_made', sanitize_text_field((string) wp_unslash($_POST['etsy_when_made'])), false);
        }
        if (isset($_POST['etsy_taxonomy_id'])) {
            update_option('aieo_feed_etsy_taxonomy_id', (string) absint(wp_unslash($_POST['etsy_taxonomy_id'])), false);
        }
        if (isset($_POST['etsy_shipping_profile_id'])) {
            update_option('aieo_feed_etsy_shipping_profile_id', (string) absint(wp_unslash($_POST['etsy_shipping_profile_id'])), false);
        }

        wp_send_json_success(array(
            'message' => __('Feed defaults saved. Re-run any feed to bake the new values in.', 'ai-eshop-optimizer'),
        ));
    }

    public function fetch_feeds() {
        global $wpdb;
        $table = $wpdb->prefix . 'aieo_dmm_feed_configs';
        if (!aieo_dmm_table_exists($table)) {
            return array();
        }
        
        $rows = $wpdb->get_results("SELECT * FROM {$table} ORDER BY enabled DESC, label ASC", ARRAY_A);
        if (!is_array($rows)) {
            return array();
        }
        foreach ($rows as &$row) {
            foreach (array('include_ids', 'include_brand_ids', 'exclude_ids', 'exclude_brand_ids', 'include_category_ids', 'exclude_category_ids', 'field_map') as $jcol) {
                $row[$jcol] = isset($row[$jcol]) ? json_decode($row[$jcol], true) : null;
                if (!is_array($row[$jcol])) {
                    $row[$jcol] = array();
                }
            }
            $row['cache_mode'] = !empty($row['cache_mode']) ? $row['cache_mode'] : 'version_query';
            $row['public_url'] = !empty($row['file_path'])
                ? self::compute_public_url($row['file_path'], $row['cache_mode'], isset($row['last_run_etag']) ? (string) $row['last_run_etag'] : '')
                : '';
            $row['file_exists'] = !empty($row['file_path']) && file_exists($row['file_path']);
        }
        return $rows;
    }

    
    public function get_templates() {
        return array(
            
            
            
            
            
            
            
            
            
            'amazon'                    => array(
                'label'         => 'Amazon Inventory Loader',
                'format'        => 'csv',
                'csv_delimiter' => "\t",
                'field_map' => array(
                    'sku'                       => 'sku',
                    'product-id'                => 'ean',
                    'product-id-type'           => 'amazon_product_id_type',
                    'price'                     => 'price',
                    'item-condition'            => 'amazon_condition_code',
                    'quantity'                  => 'stock',
                    'add-delete'                => 'amazon_add_action',
                    'will-ship-internationally' => 'amazon_ship_intl',
                    'expedited-shipping'        => 'amazon_expedited',
                    'item-note'                 => 'description',
                ),
            ),

            
            
            
            'bestprice'                 => array(
                'label'  => 'BestPrice',
                'format' => 'xml',
                'field_map' => array(
                    'uid'          => 'product_id',
                    'SKU'          => 'sku',
                    'EAN'          => 'ean',
                    'NAME'         => 'name',
                    'link'         => 'permalink',
                    'image'        => 'image_url',
                    
                    'category'     => 'category_head',
                    'price'        => 'price',
                    'instock'      => 'instock_yn',
                    'availability' => 'availability_text',
                    'size'         => 'size',
                    'manufacturer' => 'brand',
                    'weight'       => 'weight',
                    'description'  => 'description',
                    'Quantity'     => 'stock',
                ),
                
                'cdata_fields'    => array(),
                'numeric_format'  => array('weight' => '%.3f', 'price' => '%.2f'),
            ),

            
            
            
            
            
            
            
            
            'ebay'                      => array(
                'label'  => 'eBay File Exchange (CSV)',
                'format' => 'csv',
                'field_map' => array(
                    'Action'                   => 'ebay_action',
                    'CustomLabel'              => 'sku',
                    'Title'                    => 'name',
                    'Description'              => 'description',
                    'ConditionID'              => 'ebay_condition_id',
                    'PicURL'                   => 'image_url',
                    'Quantity'                 => 'stock',
                    'Format'                   => 'ebay_format',
                    'StartPrice'               => 'price',
                    'Duration'                 => 'ebay_duration',
                    'Category'                 => 'ebay_category_id',
                    'Country'                  => 'ebay_country',
                    'Location'                 => 'ebay_location',
                    'Currency'                 => 'currency_code',
                    'PayPalAccepted'           => 'ebay_paypal_accepted',
                    'ShippingService-1:Option' => 'ebay_shipping_service',
                ),
            ),

            
            
            
            
            
            
            
            
            'efood'                     => array(
                'label'     => 'eFood (catalogue JSON)',
                'format'    => 'json',
                'json_root' => 'products',
                'field_map' => array(
                    'sku'         => 'sku',
                    'title'       => 'name',
                    'description' => 'description',
                    'price'       => 'price',
                    'barcodes'    => 'ean',
                    'images'      => 'image_url',
                    'category'    => 'category',
                    'quantity'    => 'stock',
                    'active'      => 'instock_yn',
                ),
                'json_types' => array(
                    'price'    => 'number',
                    'barcodes' => 'array',
                    'images'   => 'array',
                    'quantity' => 'int',
                    'active'   => 'bool',
                ),
            ),

            
            
            
            'emag'                      => array(
                'label'  => 'EMAG',
                'format' => 'xml',
                'field_map' => array(
                    'category'      => 'category',
                    'uid'           => 'product_id',
                    'Product_name'  => 'name',
                    'Description'   => 'description',
                    'Product_link'  => 'permalink',
                    'EAN'           => 'ean',
                    'mpn'           => 'sku',
                    'Net_Price'     => 'price',
                    'Stock'         => 'stock',
                    'Pictures_Link' => 'image_url',
                    'Brand'         => 'brand',
                    'availability'  => 'availability_text',
                    'size'          => 'size',
                    'weight'        => 'weight',
                ),
                
                
                
                'cdata_fields'    => array('category', 'Product_name', 'Description', 'Product_link', 'Pictures_Link'),
                'numeric_format'  => array('weight' => '%.3f', 'Net_Price' => '%.2f'),
            ),

            
            
            
            
            
            
            
            
            
            'etsy'                      => array(
                'label'     => 'Etsy (listings JSON)',
                'format'    => 'json',
                'json_root' => 'listings',
                'field_map' => array(
                    'sku'                 => 'sku',
                    'title'               => 'name',
                    'description'         => 'description',
                    'price'               => 'price',
                    'quantity'            => 'stock',
                    'tags'                => 'tags',
                    'who_made'            => 'etsy_who_made',
                    'when_made'           => 'etsy_when_made',
                    'taxonomy_id'         => 'etsy_taxonomy_id',
                    'shipping_profile_id' => 'etsy_shipping_profile_id',
                    'is_supply'           => 'etsy_is_supply',
                    'image_urls'          => 'image_gallery_urls',
                ),
                'json_types' => array(
                    'sku'                 => 'array',
                    'price'               => 'number',
                    'quantity'            => 'int',
                    'tags'                => 'array_csv',
                    'taxonomy_id'         => 'int',
                    'shipping_profile_id' => 'int',
                    'is_supply'           => 'bool',
                    'image_urls'          => 'array_csv',
                ),
            ),

            
            
            
            
            
            
            
            
            
            
            
            'facebook_catalog'          => array(
                'label'  => 'Facebook Product Catalog',
                'format' => 'xml',
                'field_map' => array(
                    
                    
                    'g:id'                            => 'sku',
                    'g:quantity_to_sell_on_facebook'  => 'stock',
                    'g:description'                   => 'description',
                    'g:short_description'             => 'description',
                    'g:condition'                     => 'condition_new',
                    'g:mpn'                           => 'mpn',
                    'g:gtin'                          => 'ean',
                    'g:identifier_exists'             => 'identifier_exists',
                    'g:title'                         => 'name',
                    'g:availability'                  => 'availability',
                    'g:price'                         => 'price_with_currency',
                    'g:sale_price'                    => 'sale_price_with_currency',
                    'g:sale_price_effective_date'     => 'sale_price_effective_date',
                    'g:link'                          => 'permalink',
                    'g:image_link'                    => 'image_url',
                    'g:additional_image_link'         => 'additional_image',
                    'g:brand'                         => 'brand',
                    'g:shipping_weight'               => 'shipping_weight_kg',
                    'g:google_product_category'       => 'google_product_category',
                    
                    'g:product_type'                  => 'category_names_multi_path',
                    'g:item_group_id'                 => 'item_group_id',
                    'g:custom_label_0'                => 'custom_label_0_on_sale',
                    'g:custom_label_1'                => 'custom_label_1',
                ),
                
                
                'cdata_fields'   => array(
                    'g:description', 'g:short_description', 'g:title', 'g:link',
                    'g:image_link', 'g:additional_image_link', 'g:brand',
                ),
                'numeric_format' => array(),
                
                'envelope'       => 'google_local_rss',
                'per_item_tag'   => 'item',
                'item_indent'    => '    ',
                'child_indent'   => '      ',
                
                
                
                'omit_empty'     => true,
            ),

            
            
            
            
            
            
            
            
            
            
            
            'facebook_marketplace'      => array(
                'label'  => 'Facebook Marketplace',
                'format' => 'xml',
                'field_map' => array(
                    
                    
                    
                    'g:id'                            => 'sku',
                    'g:quantity_to_sell_on_facebook'  => 'stock',
                    'g:description'                   => 'description',
                    'g:condition'                     => 'condition_new',
                    'g:mpn'                           => 'mpn',
                    'g:gtin'                          => 'ean',
                    'g:identifier_exists'             => 'identifier_exists',
                    'g:title'                         => 'name',
                    'g:availability'                  => 'availability',
                    'g:price'                         => 'price_with_currency',
                    'g:sale_price'                    => 'sale_price_with_currency',
                    'g:sale_price_effective_date'     => 'sale_price_effective_date',
                    'g:link'                          => 'permalink',
                    'g:image_link'                    => 'image_url',
                    'g:additional_image_link'         => 'additional_image',
                    'g:brand'                         => 'brand',
                    'g:shipping_weight'               => 'shipping_weight_kg',
                    'g:google_product_category'       => 'google_product_category',
                    'fb_product_category'             => 'facebook_product_category',
                    'g:product_type'                  => 'category_names_multi_path',
                    'g:item_group_id'                 => 'item_group_id',
                    'g:custom_label_0'                => 'custom_label_0_on_sale',
                    'g:custom_label_1'                => 'custom_label_1',
                ),
                'cdata_fields'   => array(
                    'g:description', 'g:title', 'g:link', 'g:image_link',
                    'g:additional_image_link', 'g:brand',
                ),
                'numeric_format' => array(),
                'envelope'       => 'google_local_rss',
                'per_item_tag'   => 'item',
                'item_indent'    => '    ',
                'child_indent'   => '      ',
                'omit_empty'     => true,
            ),

            
            
            
            'generic_csv'               => array(
                'label'  => 'Generic CSV',
                'format' => 'csv',
                'field_map' => array(
                    'ProductId' => 'product_id', 'Name' => 'name', 'Url' => 'permalink',
                    'Price' => 'price', 'Stock' => 'stock', 'Brand' => 'brand', 'SKU' => 'sku',
                ),
            ),

            
            
            
            
            
            
            
            
            'google_adwords_remarketing' => array(
                'label'         => 'Google AdWords Custom Remarketing',
                'format'        => 'csv',
                'csv_delimiter' => "\t",
                'field_map' => array(
                    'ID'               => 'sku',
                    'price'            => 'price_with_currency',
                    'sale price'       => 'sale_price_with_currency',
                    'item title'       => 'name',
                    'item description' => 'description',
                    'final URL'        => 'permalink',
                    'image URL'        => 'image_url',
                    'item category'    => 'category_names',
                ),
            ),

            
            
            
            'google_local'              => array(
                'label'  => 'Google Local',
                'format' => 'xml',
                
                
                
                
                
                
                'field_map' => array(
                    'g:id'           => 'sku',
                    'g:store_code'   => '_location_code',
                    'g:availability' => 'google_availability',
                    'g:price'        => 'regular_price_eur',
                    'g:sale_price'   => 'sale_price_eur',
                ),
                'cdata_fields'   => array(),
                'numeric_format' => array(),
                
                
                
                'envelope'       => 'google_local_rss',
                'per_item_tag'   => 'item',
                'item_indent'    => '    ',
                'child_indent'   => '      ',
                
                
                
                'tag_attrs'      => 'xmlns:g="http://base.google.com/ns/1.0"',
                
                
                
                'omit_empty'     => true,
            ),

            
            
            
            
            
            
            
            
            'google_shopping'           => array(
                'label'  => 'Google Shopping',
                'format' => 'xml',
                'field_map' => array(
                    
                    
                    'g:id'                          => 'sku',
                    'g:title'                       => 'name',
                    'g:description'                 => 'description',
                    'g:link'                        => 'permalink',
                    'g:image_link'                  => 'image_url',
                    'g:additional_image_link'       => 'additional_image',
                    'g:availability'                => 'availability',
                    'g:price'                       => 'price_with_currency',
                    
                    
                    
                    'g:sale_price'                  => 'sale_price_with_currency',
                    'g:sale_price_effective_date'   => 'sale_price_effective_date',
                    'g:brand'                       => 'brand',
                    'g:mpn'                         => 'mpn',
                    'g:gtin'                        => 'ean',
                    'g:identifier_exists'           => 'identifier_exists',
                    'g:condition'                   => 'condition_new',
                    'g:google_product_category'     => 'google_product_category',
                    
                    'g:product_type'                => 'category_names_multi_path',
                    'g:shipping_weight'             => 'shipping_weight_kg',
                    'g:item_group_id'               => 'item_group_id',
                    'g:custom_label_0'              => 'custom_label_0_on_sale',
                ),
                'cdata_fields'   => array(
                    'g:title', 'g:description', 'g:link', 'g:image_link',
                    'g:additional_image_link', 'g:brand',
                ),
                'numeric_format' => array(),
                'envelope'       => 'google_local_rss',
                'per_item_tag'   => 'item',
                'item_indent'    => '    ',
                'child_indent'   => '      ',
                'omit_empty'     => true,
            ),

            
            
            
            'shopflix'                  => array(
                'label'  => 'Shopflix',
                'format' => 'xml',
                
                
                
                
                
                'field_map' => array(
                    'uid'                => 'product_id',
                    'SKU'                => 'sku',
                    'EAN'                => 'ean',
                    'NAME'               => 'name',
                    'ProductURL'         => 'permalink',
                    'image'              => 'image_url',
                    'Additional_Image'   => 'additional_image',
                    
                    
                    'category'           => 'category_head',
                    'Price'              => 'price',
                    'instock'            => 'instock_yn',
                    'availability'       => 'availability_text',
                    'Color'              => 'color',
                    'Manufacturer'       => 'brand',
                    'Shipping_Lead_Time' => 'shipping_lead_time',
                    'weight'             => 'weight',
                    'Description'        => 'description',
                    'Quantity'           => 'stock',
                ),
                
                'cdata_fields'    => array(),
                'numeric_format'  => array('weight' => '%.3f', 'Price' => '%.2f'),
            ),

            
            
            
            'skroutz'                   => array(
                'label'  => 'Skroutz',
                'format' => 'xml',
                
                
                
                
                
                
                'field_map' => array(
                    'uid'              => 'product_id',
                    'mpn'              => 'sku',
                    'ean'              => 'ean',
                    'name'             => 'name',
                    'link'             => 'permalink',
                    'image'            => 'image_url',
                    'additional_image' => 'additional_image',
                    'category'         => 'category',
                    'price'            => 'price',
                    'instock'          => 'instock_yn',
                    'availability'     => 'availability_text',
                    'quantity'         => 'stock',
                    'color'            => 'color',
                    'size'             => 'size',
                    'manufacturer'     => 'brand',
                    'weight'           => 'weight',
                    'description'      => 'description',
                ),
                
                
                
                
                'cdata_fields'    => array('name', 'link', 'image', 'additional_image', 'category', 'color', 'description'),
                
                
                'numeric_format'  => array('weight' => '%.3f', 'price' => '%.2f'),
            ),

            
            
            
            'tolstoy'                   => array(
                'label'  => 'Tolstoy (CSV catalog)',
                'format' => 'csv',
                
                
                
                
                'field_map' => array(
                    'id'              => 'product_id',
                    'title'           => 'name',
                    'url'             => 'permalink',
                    'imageUrl'        => 'image_url',
                    'price'           => 'price',
                    'descriptionHtml' => 'description_html',
                    'images'          => 'image_gallery_urls',
                    'compareAtPrice'  => 'regular_price',
                    'currencyCode'    => 'currency_code',
                    'currencySymbol'  => 'currency_symbol',
                    'inventory'       => 'stock',
                ),
            ),

            
            
            
            
            
            
            
            
            'wolt'                      => array(
                'label'     => 'Wolt (menu JSON)',
                'format'    => 'json',
                'json_root' => 'items',
                'field_map' => array(
                    'name'          => 'name',
                    'description'   => 'description',
                    'price'         => 'price',
                    'image_url'     => 'image_url',
                    'external_data' => 'sku',
                    'gtin'          => 'ean',
                    'category'      => 'category',
                    'enabled'       => 'instock_yn',
                    'quantity'      => 'stock',
                ),
                'json_types' => array(
                    'price'    => 'number',
                    'enabled'  => 'bool',
                    'quantity' => 'int',
                ),
            ),
        );
    }

    public function ajax_save() {
        AIEO_DMM::verify_ajax_request();

        global $wpdb;
        $table = $wpdb->prefix . 'aieo_dmm_feed_configs';
        if (!aieo_dmm_table_exists($table)) {
            wp_send_json_error(array('message' => __('Table missing.', 'ai-eshop-optimizer')));
        }

        $id = isset($_POST['id']) ? absint(wp_unslash($_POST['id'])) : 0;
        $slug = isset($_POST['slug']) ? sanitize_key(wp_unslash($_POST['slug'])) : '';
        $label = isset($_POST['label']) ? sanitize_text_field(wp_unslash($_POST['label'])) : '';
        $format = isset($_POST['format']) ? sanitize_key(wp_unslash($_POST['format'])) : 'xml';
        $format = in_array($format, array('csv', 'xml', 'json'), true) ? $format : 'xml';
        $template = isset($_POST['template']) ? sanitize_key(wp_unslash($_POST['template'])) : '';
        $enabled = !empty($_POST['enabled']) ? 1 : 0;
        $schedule = isset($_POST['schedule']) ? sanitize_key(wp_unslash($_POST['schedule'])) : 'manual';

        $cache_mode_allowed = array('none', 'version_query', 'unique_filename');
        $cache_mode = isset($_POST['cache_mode']) ? sanitize_key(wp_unslash($_POST['cache_mode'])) : 'version_query';
        if (!in_array($cache_mode, $cache_mode_allowed, true)) {
            $cache_mode = 'version_query';
        }

        if ($slug === '' || $label === '') {
            wp_send_json_error(array('message' => __('Slug and label are required.', 'ai-eshop-optimizer')));
        }

        
        
        $dir = AIEO_DMM::get_exports_path();
        $file_path = trailingslashit($dir['path']) . $slug . '.' . $format;

        
        
        $include_ids           = $this->collect_int_param('include_ids');
        $include_brand_ids     = $this->collect_brand_param('include_brand_ids');
        $exclude_ids           = $this->collect_int_param('exclude_ids');
        $exclude_brand_ids     = $this->collect_brand_param('exclude_brand_ids');
        $include_category_ids  = $this->collect_int_param('include_category_ids');
        $exclude_category_ids  = $this->collect_int_param('exclude_category_ids');
        
        
        $location_code = isset($_POST['location_code'])
            ? preg_replace('/[^A-Za-z0-9_-]/', '', wp_unslash((string) sanitize_text_field( wp_unslash( $_POST['location_code'] ) )))
            : '';
        
        
        $exclude_auto_restocked = !empty($_POST['exclude_auto_restocked']) ? 1 : 0;

        $field_map = array();
        $raw_map = isset($_POST['field_map']) && is_array($_POST['field_map']) ? map_deep(wp_unslash($_POST['field_map']), 'sanitize_text_field') : array();
        foreach ($raw_map as $output_name => $source_key) {
            $output = trim((string) $output_name);
            $source = sanitize_key((string) $source_key);
            if ($output === '' || $source === '') {
                continue;
            }
            $field_map[$output] = $source;
        }

        $data = array(
            'slug'                 => $slug,
            'label'                => $label,
            'format'               => $format,
            'template'             => $template,
            'file_path'            => $file_path,
            'include_ids'          => wp_json_encode($include_ids),
            'include_brand_ids'    => wp_json_encode($include_brand_ids),
            'exclude_ids'          => wp_json_encode($exclude_ids),
            'exclude_brand_ids'    => wp_json_encode($exclude_brand_ids),
            'include_category_ids' => wp_json_encode($include_category_ids),
            'exclude_category_ids' => wp_json_encode($exclude_category_ids),
            'field_map'            => wp_json_encode($field_map),
            'schedule'             => in_array($schedule, array('manual','hourly','twicedaily','daily'), true) ? $schedule : 'manual',
            'cache_mode'           => $cache_mode,
            'enabled'              => $enabled,
            'exclude_auto_restocked' => $exclude_auto_restocked,
            'location_code'        => $location_code,
        );
        $formats = array('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%d','%d','%s');

        
        
        
        self::ensure_location_code_column();
        if (function_exists('aieo_dmm_ensure_column')) {
            aieo_dmm_ensure_column($table, 'exclude_auto_restocked', 'tinyint(1) NOT NULL DEFAULT 0');
        }

        if ($id > 0) {
            $wpdb->update($table, $data, array('id' => $id), $formats, array('%d'));
        } else {
            $existing = $wpdb->get_var($wpdb->prepare(
                
                "SELECT id FROM {$table} WHERE slug = %s",
                $slug
            ));
            if ($existing) {
                wp_send_json_error(array('message' => __('A feed with that slug already exists.', 'ai-eshop-optimizer')));
            }
            $wpdb->insert($table, $data, $formats);
            $id = (int) $wpdb->insert_id;
        }

        
        wp_clear_scheduled_hook(self::CRON_HOOK, array($id));
        if ($enabled && $data['schedule'] !== 'manual') {
            wp_schedule_event(time() + 600, $data['schedule'], self::CRON_HOOK, array($id));
        }

        
        
        
        wp_send_json_success(array(
            'id'        => $id,
            'message'   => __('Feed saved.', 'ai-eshop-optimizer'),
            'list_html' => $this->render_feeds_table_html(),
        ));
    }

    
    public function render_feeds_table_html(): string {
        $feeds = $this->fetch_feeds();
        ob_start();
        $partial = AIEO_DIR . 'assets/pages/dmm/_marketplace-feeds-table.php';
        if (file_exists($partial)) {
            include $partial;
        }
        return (string) ob_get_clean();
    }

    
    public static function ensure_location_code_column() {
        global $wpdb;
        $table = $wpdb->prefix . 'aieo_dmm_feed_configs';
        
        $exists = (string) $wpdb->get_var("SHOW COLUMNS FROM {$table} LIKE 'location_code'");
        if (!$exists) {
            
            $wpdb->query("ALTER TABLE {$table} ADD COLUMN location_code VARCHAR(64) DEFAULT NULL AFTER cache_mode");
        }
    }

    public function ajax_delete() {
        AIEO_DMM::verify_ajax_request();
        $id = isset($_POST['id']) ? absint(wp_unslash($_POST['id'])) : 0;
        if ($id <= 0) {
            wp_send_json_error(array('message' => __('Invalid id.', 'ai-eshop-optimizer')));
        }
        wp_clear_scheduled_hook(self::CRON_HOOK, array($id));
        global $wpdb;
        $wpdb->delete($wpdb->prefix . 'aieo_dmm_feed_configs', array('id' => $id), array('%d'));
        wp_send_json_success(array('list_html' => $this->render_feeds_table_html()));
    }

    public function ajax_run_now() {
        AIEO_DMM::verify_ajax_request();
        $id = isset($_POST['id']) ? absint(wp_unslash($_POST['id'])) : 0;
        if ($id <= 0) {
            wp_send_json_error(array('message' => __('Invalid id.', 'ai-eshop-optimizer')));
        }
        $result = $this->generate_feed_by_id($id);
        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
        }
        
        $result['generated_at'] = current_time('mysql', true);
        wp_send_json_success($result);
    }

    
    public function generate_feed_by_id($id) {
        $id = (int) $id;
        if ($id <= 0) {
            return new WP_Error('bad_id', __('Invalid feed id.', 'ai-eshop-optimizer'));
        }

        
        
        
        
        
        
        
        
        
        
        
        
        $lock_key = 'aieo_dmm_feed_gen_lock_' . $id;
        if (get_transient($lock_key)) {
            return new WP_Error('feed_already_running', sprintf(
                
                __('Feed %d is already being generated by another process — skipping concurrent run.', 'ai-eshop-optimizer'),
                $id
            ));
        }
        set_transient($lock_key, time(), 10 * MINUTE_IN_SECONDS);

        
        
        
        
        register_shutdown_function(function () use ($lock_key) {
            $err = error_get_last();
            $fatal_types = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);
            if ($err && in_array($err['type'], $fatal_types, true)) {
                delete_transient($lock_key);
            }
        });

        global $wpdb;
        $table = $wpdb->prefix . 'aieo_dmm_feed_configs';
        
        $feed = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $id), ARRAY_A);
        if (!$feed) {
            delete_transient($lock_key);
            return new WP_Error('not_found', __('Feed not found.', 'ai-eshop-optimizer'));
        }

        foreach (array('include_ids', 'include_brand_ids', 'exclude_ids', 'exclude_brand_ids', 'field_map') as $jcol) {
            $feed[$jcol] = json_decode($feed[$jcol] ?? '[]', true) ?: array();
        }

        
        
        
        if (!empty($feed['exclude_auto_restocked']) && class_exists('AIEO_DMM_Physical_Recovery')) {
            AIEO_DMM_Physical_Recovery::sync_auto_restock_vitals_flag();
        }

        $format = in_array($feed['format'], array('csv', 'xml', 'json'), true) ? $feed['format'] : 'xml';
        $cache_mode = isset($feed['cache_mode']) ? (string) $feed['cache_mode'] : 'version_query';
        $path = $feed['file_path'];

        
        
        if ($cache_mode === 'unique_filename') {
            $token = substr(md5($feed['slug'] . microtime(true) . wp_rand()), 0, 8);
            $parent_dir = dirname($path);
            $basename = $feed['slug'] . '-' . $token . '.' . $format;
            $path = trailingslashit($parent_dir) . $basename;
        }

        $parent_dir = dirname($path);
        if (!is_dir($parent_dir)) {
            wp_mkdir_p($parent_dir);
        }

        $handle = fopen($path, 'w');
        if (!$handle) {
            delete_transient($lock_key);
            return new WP_Error('io', __('Cannot write feed file.', 'ai-eshop-optimizer'));
        }

        $field_map = is_array($feed['field_map']) && !empty($feed['field_map']) ? $feed['field_map'] : array();
        
        
        
        $tpl_slug      = (string) ($feed['template'] ?? '');
        $templates     = $this->get_templates();
        $tpl_default   = isset($templates[$tpl_slug]) ? $templates[$tpl_slug] : array();
        if (empty($field_map) && !empty($tpl_default['field_map'])) {
            $field_map = $tpl_default['field_map'];
        }
        $cdata_fields  = isset($feed['cdata_fields']) && is_array($feed['cdata_fields'])
            ? $feed['cdata_fields']
            : (isset($tpl_default['cdata_fields']) ? (array) $tpl_default['cdata_fields'] : array());
        $numeric_fmt   = isset($feed['numeric_format']) && is_array($feed['numeric_format'])
            ? $feed['numeric_format']
            : (isset($tpl_default['numeric_format']) ? (array) $tpl_default['numeric_format'] : array());
        $cdata_lookup  = array_flip($cdata_fields);
        
        
        
        $envelope_kind = (string) ($tpl_default['envelope']     ?? 'webstore');
        $per_item_tag  = (string) ($tpl_default['per_item_tag']  ?? 'product');
        $item_indent   = (string) ($tpl_default['item_indent']   ?? '    ');
        $child_indent  = (string) ($tpl_default['child_indent']  ?? '      ');
        $tag_attrs     = (string) ($tpl_default['tag_attrs']     ?? '');
        $omit_empty    = !empty($tpl_default['omit_empty']);
        
        
        
        $csv_delim_raw = (string) ($tpl_default['csv_delimiter'] ?? ',');
        $csv_delimiter = $csv_delim_raw === '\t' ? "\t" : $csv_delim_raw;

        
        
        
        $location_code = '';
        if ($tpl_slug === 'google_local') {
            if (!empty($feed['location_code'])) {
                $location_code = (string) $feed['location_code'];
            } elseif (!empty($feed['slug']) && strpos($feed['slug'], 'google-local-') === 0) {
                $location_code = substr($feed['slug'], strlen('google-local-'));
            }
        }

        if (empty($field_map)) {
            fclose($handle);
            delete_transient($lock_key);
            return new WP_Error('no_fields', __('Configure at least one field.', 'ai-eshop-optimizer'));
        }

        if ($format === 'csv') {
            
            AIEO_DMM_Sample::write_csv_bom($handle);
            fputcsv($handle, array_keys($field_map), $csv_delimiter);
        } elseif ($format === 'json') {
            
            
            
            
            $json_root = (string) ($tpl_default['json_root'] ?? '');
            fwrite($handle, $json_root !== '' ? '{"' . $json_root . '":[' : '[');
        } elseif ($envelope_kind === 'google_local_rss') {
            
            
            $title  = function_exists('get_bloginfo') ? (string) get_bloginfo('name') : 'Products';
            $link   = function_exists('home_url') ? trailingslashit((string) home_url('/')) : '';
            fwrite($handle, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            fwrite($handle, "<rss xmlns:g=\"http://base.google.com/ns/1.0\" version=\"2.0\">\n");
            fwrite($handle, "  <channel>\n");
            fwrite($handle, "    <title><![CDATA[" . $title . " Products]]></title>\n");
            fwrite($handle, "    <link><![CDATA[" . $link . "]]></link>\n");
            fwrite($handle, "    <description>WooCommerce Product List RSS feed</description>\n");
        } else {
            
            
            
            
            $xml_root = (string) apply_filters('aieo_dmm_feed_xml_root', 'webstore', $feed);
            $items_tag = ($tpl_slug === 'emag') ? 'Products' : 'products';
            $items_tag = (string) apply_filters('aieo_dmm_feed_xml_items_tag', $items_tag, $feed);
            $created  = current_time('Y-m-d H:i');
            fwrite($handle, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            fwrite($handle, "<{$xml_root}>\n");
            fwrite($handle, "  <created_at>{$created}</created_at>\n");
            fwrite($handle, "  <{$items_tag}>\n");
        }

        $count = 0;
        $batch_size = 500;
        $offset = 0;

        
        
        
        
        
        while (true) {
            $vitals_rows = $this->query_vitals_batch($feed, $batch_size, $offset);
            if (empty($vitals_rows)) {
                break;
            }
            foreach ($vitals_rows as $vitals) {
                
                if ($location_code !== '') {
                    $vitals['_location_code'] = $location_code;
                }

                $row = $this->build_row_from_vitals($vitals, $field_map);
                $row = apply_filters('aieo_dmm_feed_item', $row, $feed['slug'], $feed);

                if ($format === 'csv') {
                    fputcsv($handle, array_values($row), $csv_delimiter);
                } elseif ($format === 'json') {
                    $obj = $this->build_json_item($row, $tpl_default);
                    fwrite($handle, ($count > 0 ? ',' : '') . wp_json_encode($obj, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
                } else {
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    $tag_attrs_suffix = $tag_attrs !== '' ? ' ' . $tag_attrs : '';
                    $xml = $item_indent . '<' . $per_item_tag . ">\n";
                    foreach ($row as $tag => $val) {
                        $safe_tag = preg_replace('/[^A-Za-z0-9_:-]/', '', (string) $tag);
                        if ($safe_tag === '') continue;
                        $val_str = (string) $val;
                        
                        
                        
                        if (isset($numeric_fmt[$tag]) && $val_str !== '' && is_numeric($val_str)) {
                            $val_str = sprintf((string) $numeric_fmt[$tag], (float) $val_str);
                        }
                        if ($val_str === '') {
                            if ($omit_empty) { continue; }
                            $xml .= $child_indent . '<' . $safe_tag . $tag_attrs_suffix . "/>\n";
                            continue;
                        }
                        if (isset($cdata_lookup[$tag])) {
                            
                            
                            
                            
                            
                            $cdata_safe = str_replace(']]>', ']]]]><![CDATA[>', $val_str);
                            $xml .= $child_indent . '<' . $safe_tag . $tag_attrs_suffix . '><![CDATA[' . $cdata_safe . ']]></' . $safe_tag . ">\n";
                        } else {
                            
                            
                            
                            $clean = html_entity_decode($val_str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
                            $xml .= $child_indent . '<' . $safe_tag . $tag_attrs_suffix . '>' . AIEO_DMM_Price_Export::xml_escape($clean) . '</' . $safe_tag . ">\n";
                        }
                    }
                    $xml .= $item_indent . '</' . $per_item_tag . ">\n";
                    fwrite($handle, $xml);
                }
                $count++;
            }
            $offset += $batch_size;
        }

        if ($format === 'xml') {
            if ($envelope_kind === 'google_local_rss') {
                fwrite($handle, "  </channel>\n");
                fwrite($handle, "</rss>\n");
            } else {
                $xml_root = (string) apply_filters('aieo_dmm_feed_xml_root', 'webstore', $feed);
                $items_tag = ($tpl_slug === 'emag') ? 'Products' : 'products';
                $items_tag = (string) apply_filters('aieo_dmm_feed_xml_items_tag', $items_tag, $feed);
                fwrite($handle, "  </{$items_tag}>\n");
                fwrite($handle, "</{$xml_root}>\n");
            }
        } elseif ($format === 'json') {
            $json_root = (string) ($tpl_default['json_root'] ?? '');
            fwrite($handle, $json_root !== '' ? ']}' : ']');
        }
        fclose($handle);

        
        $etag = md5_file($path);
        $etag_short = $etag ? substr($etag, 0, 10) : (string) time();

        
        $wpdb->update($table, array(
            'file_path'       => $path,
            'last_run_at'     => current_time('mysql', true),
            'last_run_status' => 'ok',
            'last_run_count'  => $count,
            'last_run_etag'   => $etag_short,
        ), array('id' => (int) $feed['id']), array('%s','%s','%s','%d','%s'), array('%d'));

        
        $public_url = self::compute_public_url($path, $cache_mode, $etag_short);

        
        
        if ($cache_mode === 'unique_filename') {
            self::purge_old_feed_files(dirname($path), $feed['slug'], basename($path), $format);
        }

        delete_transient($lock_key);
        return array(
            'path'       => $path,
            'public_url' => $public_url,
            'count'      => $count,
            'etag'       => $etag_short,
        );
    }

    
    private function build_json_item($row, $tpl) {
        $types = isset($tpl['json_types']) && is_array($tpl['json_types']) ? $tpl['json_types'] : array();
        $out   = array();
        foreach ($row as $key => $val) {
            $type       = isset($types[$key]) ? (string) $types[$key] : 'string';
            $out[$key]  = self::coerce_json_value((string) $val, $type);
        }
        return $out;
    }

    
    private static function coerce_json_value($s, $type) {
        switch ($type) {
            case 'number':
                return $s === '' ? 0 : (float) $s;
            case 'int':
                return (int) $s;
            case 'bool':
                return in_array(strtolower(trim($s)), array('1', 'y', 'yes', 'true', 'instock', 'in stock'), true);
            case 'array':
                return $s === '' ? array() : array(self::json_text($s));
            case 'array_csv':
                if ($s === '') { return array(); }
                return array_values(array_filter(array_map(function ($p) { return self::json_text(trim($p)); }, explode(',', $s))));
            default:
                return self::json_text($s);
        }
    }

    
    private static function json_text($s) {
        return html_entity_decode((string) $s, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }

    
    public static function compute_public_url($path, $cache_mode, $etag = '') {
        $upload = wp_upload_dir();
        $basedir = trailingslashit($upload['basedir']);
        $baseurl = trailingslashit($upload['baseurl']);

        $rel = ltrim(str_replace($basedir, '', $path), '/');
        $url = $baseurl . $rel;

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        $url .= (strpos($url, '?') === false ? '?' : '&') . 'cf-no-cache=1';
        return $url;
    }

    
    private static function purge_old_feed_files($dir, $slug, $keep_basename, $format) {
        $pattern = trailingslashit($dir) . $slug . '-*.' . $format;
        $matches = glob($pattern);
        if (!is_array($matches)) { return; }
        foreach ($matches as $file) {
            if (basename($file) === $keep_basename) { continue; }
            @unlink($file); 
        }
    }

    
    private function query_vitals_batch(array $feed, $batch_size, $offset) {
        global $wpdb;
        $tbl = $wpdb->prefix . 'aieo_core_product_vitals';

        
        
        
        
        
        
        
        $where  = array(
            "v.status = 1",
            "v.is_variation_parent = 0",
            "v.name IS NOT NULL AND v.name <> ''",
            "v.sku IS NOT NULL AND v.sku <> ''",
            "v.gtin IS NOT NULL AND v.gtin <> ''",
            "v.image_url IS NOT NULL AND v.image_url <> ''",
            "(COALESCE(v.sub_category, '') <> '' OR COALESCE(v.head_category, '') <> '' OR COALESCE(v.menu_category, '') <> '')",
            
            
            
            
            "v.weight IS NOT NULL AND v.weight > 0",
            
            
            
            "v.stock IS NOT NULL AND v.stock > 0",
            
            
            
            "(COALESCE(v.brand, '') <> '' OR COALESCE(p.brand, '') <> '')",
        );
        $params = array();

        if (!empty($feed['include_ids'])) {
            $placeholders = implode(',', array_fill(0, count($feed['include_ids']), '%d'));
            
            
            $where[] = "(v.product_id IN ({$placeholders}) OR v.parent_id IN ({$placeholders}))";
            $args = array_map('intval', $feed['include_ids']);
            $params = array_merge($params, $args, $args);
        }
        if (!empty($feed['exclude_ids'])) {
            $placeholders = implode(',', array_fill(0, count($feed['exclude_ids']), '%d'));
            
            
            $where[] = "v.product_id NOT IN ({$placeholders}) AND v.parent_id NOT IN ({$placeholders})";
            $args = array_map('intval', $feed['exclude_ids']);
            $params = array_merge($params, $args, $args);
        }
        if (!empty($feed['include_brand_ids'])) {
            $placeholders = implode(',', array_fill(0, count($feed['include_brand_ids']), '%d'));
            
            
            $where[] = "COALESCE(v.brand_id, p.brand_id) IN ({$placeholders})";
            $params = array_merge($params, array_map('intval', $feed['include_brand_ids']));
        }
        if (!empty($feed['exclude_brand_ids'])) {
            $placeholders = implode(',', array_fill(0, count($feed['exclude_brand_ids']), '%d'));
            $where[] = "(COALESCE(v.brand_id, p.brand_id) IS NULL OR COALESCE(v.brand_id, p.brand_id) NOT IN ({$placeholders}))";
            $params = array_merge($params, array_map('intval', $feed['exclude_brand_ids']));
        }
        if (!empty($feed['exclude_auto_restocked'])) {
            
            
            
            $where[] = "(v.is_auto_restocked IS NULL OR v.is_auto_restocked = 0)";
        }

        $where_sql = 'WHERE ' . implode(' AND ', $where);
        
        $sql = "SELECT v.*, p.brand_id AS parent_brand_id FROM {$tbl} v "
             . "LEFT JOIN {$tbl} p ON p.product_id = v.parent_id AND p.is_variation_parent = 1 "
             . "{$where_sql} ORDER BY v.product_id ASC LIMIT %d OFFSET %d";
        $params[] = (int) $batch_size;
        $params[] = (int) $offset;

        $rows = $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A);
        return is_array($rows) ? $rows : array();
    }

    
    private function build_row_from_vitals(array $vitals, array $field_map) {
        $row = array();
        foreach ($field_map as $output_name => $source_key) {
            $row[$output_name] = $this->field_source_from_vitals($source_key, $vitals);
        }
        return $row;
    }

    
    private function field_source_from_vitals($key, array $v) {
        switch ($key) {
            
            case 'product_id':         return (string) ($v['product_id'] ?? '');
            case 'sku':                return (string) ($v['sku'] ?? ($v['parent_sku'] ?? ''));
            case 'mpn':
                $mpn = (string) ($v['sku'] ?? '');
                return $mpn !== '' ? $mpn : (string) ($v['parent_sku'] ?? '');
            case 'ean':                return (string) ($v['gtin'] ?? '');

            
            
            
            
            
            case 'etsy_who_made':            return (string) get_option('aieo_feed_etsy_who_made', 'i_did');
            case 'etsy_when_made':           return (string) get_option('aieo_feed_etsy_when_made', 'made_to_order');
            case 'etsy_taxonomy_id':         return (string) get_option('aieo_feed_etsy_taxonomy_id', '');
            case 'etsy_shipping_profile_id': return (string) get_option('aieo_feed_etsy_shipping_profile_id', '');
            case 'etsy_is_supply':           return 'false';

            
            case 'name':               return (string) ($v['name'] ?? ($v['product_name'] ?? ''));
            case 'permalink':
                
                
                
                
                
                $u = (string) ($v['product_url'] ?? '');
                if ($u !== '' && ! preg_match('#^https?://#i', $u)) {
                    $u = function_exists('home_url') ? home_url('/' . ltrim($u, '/')) : $u;
                }
                return $u;
            case 'image_url':          return (string) ($v['image_url'] ?? '');
            case 'image_gallery_urls': return (string) ($v['image_gallery_urls'] ?? '');
            case 'additional_image':
                
                
                
                $gal = (string) ($v['image_gallery_urls'] ?? '');
                if ($gal === '') { return ''; }
                $first = strtok($gal, ',');
                return $first !== false ? trim($first) : '';

            
            
            case 'category':
                $parts = array_filter(array(
                    (string) ($v['menu_category'] ?? ''),
                    (string) ($v['head_category'] ?? ''),
                    (string) ($v['sub_category']  ?? ''),
                ));
                return implode('>', $parts);
            case 'category_leaf':
                foreach (array('sub_category', 'head_category', 'menu_category') as $col) {
                    $val = (string) ($v[$col] ?? '');
                    if ($val !== '') { return $val; }
                }
                return '';
            case 'category_head':
                
                
                
                
                foreach (array('head_category', 'sub_category', 'menu_category') as $col) {
                    $val = (string) ($v[$col] ?? '');
                    if ($val !== '') { return $val; }
                }
                return '';
            case 'top_category':       return (string) ($v['menu_category'] ?? '');
            case 'tags':               return (string) ($v['tag_names'] ?? '');

            
            case 'brand':              return (string) ($v['brand'] ?? '');

            
            case 'price':              return $this->scalar_or_empty($v['price'] ?? null);
            case 'regular_price':      return $this->scalar_or_empty($v['regular_price'] ?? null);
            case 'sale_price':
                $reg = (float) ($v['regular_price'] ?? 0);
                $cur = (float) ($v['price'] ?? 0);
                return ($cur > 0 && $reg > $cur) ? $this->scalar_or_empty($cur) : '';

            
            
            
            case '_location_code':
                return (string) ($v['_location_code'] ?? '');
            case 'google_availability':
                return ((int) ($v['stock'] ?? 0) > 0 || ($v['stock_status'] ?? '') === 'instock')
                    ? 'in stock' : 'out of stock';
            case 'regular_price_eur':
                $rp = (float) ($v['regular_price'] ?? 0);
                if ($rp <= 0) { $rp = (float) ($v['price'] ?? 0); }
                return $rp > 0 ? sprintf('%.2f EUR', $rp) : '';
            case 'sale_price_eur':
                $reg = (float) ($v['regular_price'] ?? 0);
                $cur = (float) ($v['price'] ?? 0);
                return ($cur > 0 && $reg > 0 && $cur < $reg)
                    ? sprintf('%.2f EUR', $cur)
                    : '';

            
            case 'stock':
                $s = (int) ($v['stock'] ?? 0);
                return (string) max(0, $s);
            case 'instock_yn':
                return ((int) ($v['stock'] ?? 0) > 0 || ($v['stock_status'] ?? '') === 'instock') ? 'Y' : 'N';
            case 'availability':
                return ((int) ($v['stock'] ?? 0) > 0 || ($v['stock_status'] ?? '') === 'instock')
                    ? 'in stock' : 'out of stock';
            case 'availability_text':
                $in_stock = ((int) ($v['stock'] ?? 0) > 0 || ($v['stock_status'] ?? '') === 'instock');
                if (!$in_stock) {
                    return (string) get_option('aieo_feed_availability_oos', __('Μη διαθέσιμο', 'ai-eshop-optimizer'));
                }
                return (string) get_option('aieo_feed_availability_instock', __('Παράδοση σε 1 - 3 ημέρες', 'ai-eshop-optimizer'));
            case 'shipping_lead_time':
                return (string) get_option('aieo_feed_default_lead_time', '0');

            
            case 'weight': return $this->scalar_or_empty($v['weight'] ?? null);
            case 'length': return $this->scalar_or_empty($v['length'] ?? null);
            case 'width':  return $this->scalar_or_empty($v['width']  ?? null);
            case 'height': return $this->scalar_or_empty($v['height'] ?? null);
            case 'color':  return (string) ($v['color'] ?? '');
            case 'size':   return (string) ($v['size']  ?? '');

            
            case 'description':
                $sd = (string) ($v['short_description'] ?? '');
                if ($sd !== '') { return $sd; }
                $ld = (string) ($v['product_description'] ?? '');
                return mb_substr(wp_strip_all_tags($ld), 0, 280);
            case 'description_long':
                return wp_strip_all_tags((string) ($v['product_description'] ?? ''));
            case 'description_html':
                
                $sd = (string) ($v['short_description'] ?? '');
                if ($sd !== '') { return $sd; }
                return (string) ($v['product_description'] ?? '');

            
            
            
            case 'currency_code':
                if (!isset($this->cached_currency_code)) {
                    $this->cached_currency_code = function_exists('get_woocommerce_currency')
                        ? (string) get_woocommerce_currency() : 'EUR';
                }
                return $this->cached_currency_code;
            case 'currency_symbol':
                if (!isset($this->cached_currency_symbol)) {
                    $code = function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'EUR';
                    $this->cached_currency_symbol = function_exists('get_woocommerce_currency_symbol')
                        ? html_entity_decode((string) get_woocommerce_currency_symbol($code), ENT_QUOTES, 'UTF-8')
                        : '€';
                }
                return $this->cached_currency_symbol;

            
            
            
            case 'condition_new':
                
                
                
                $c = (string) ($v['condition'] ?? '');
                return $c !== '' ? $c : 'new';
            case 'price_with_currency':
                
                
                
                
                $reg = (float) ($v['regular_price'] ?? 0);
                $p   = $reg > 0 ? $reg : (float) ($v['price'] ?? 0);
                if ($p <= 0) { return ''; }
                $code = $this->field_source_from_vitals('currency_code', $v);
                return sprintf('%.2f %s', $p, $code);
            case 'sale_price_with_currency':
                $reg = (float) ($v['regular_price'] ?? 0);
                $cur = (float) ($v['price'] ?? 0);
                if (!($cur > 0 && $reg > 0 && $cur < $reg)) { return ''; }
                $code = $this->field_source_from_vitals('currency_code', $v);
                return sprintf('%.2f %s', $cur, $code);
            case 'sale_price_effective_date':
                
                
                
                
                
                
                
                return '';
            case 'identifier_exists':
                
                
                
                return ((string) ($v['gtin'] ?? '')) !== '' ? '' : 'no';
            case 'category_names_multi_path':
                
                
                
                
                
                $chain = array();
                foreach (array('menu_category', 'head_category', 'sub_category') as $col) {
                    $val = (string) ($v[$col] ?? '');
                    if ($val !== '') { $chain[] = $val; }
                }
                if (empty($chain)) { return ''; }
                $paths = array();
                for ($i = 1, $n = count($chain); $i <= $n; $i++) {
                    $paths[] = implode(' > ', array_slice($chain, 0, $i));
                }
                return implode(' , ', $paths);
            case 'custom_label_0_on_sale':
                
                
                
                
                $reg = (float) ($v['regular_price'] ?? 0);
                $cur = (float) ($v['price'] ?? 0);
                return ($cur > 0 && $reg > 0 && $cur < $reg) ? 'on-sale' : '';
            case 'shipping_weight_kg':
                
                $w = (float) ($v['weight'] ?? 0);
                return $w > 0 ? sprintf('%.3f kg', $w) : '';
            case 'google_product_category':
                
                
                
                
                
                $val = (string) ($v['google_product_category'] ?? '');
                if ($val !== '') { return $val; }
                return (string) get_option('aieo_feed_default_google_product_category', '');
            case 'facebook_product_category':
                
                
                
                $val = (string) ($v['facebook_product_category'] ?? '');
                if ($val !== '') { return $val; }
                return (string) get_option('aieo_feed_default_facebook_product_category', '');
            case 'item_group_id':
                
                
                
                $parent = (int) ($v['parent_id'] ?? 0);
                $self   = (int) ($v['product_id'] ?? 0);
                return ($parent > 0 && $parent !== $self) ? (string) $parent : '';
            case 'custom_label_0':
                return (string) ($v['custom_label_0'] ?? '');
            case 'custom_label_1':
                return (string) ($v['custom_label_1'] ?? '');
            case 'category_names':
                
                return (string) ($v['category_names'] ?? '');

            
            
            
            case 'amazon_product_id_type':
                
                return '4';
            case 'amazon_condition_code':
                
                return '11';
            case 'amazon_add_action':
                
                return 'a';
            case 'amazon_ship_intl':
            case 'amazon_expedited':
                
                
                
                return '';

            
            
            
            case 'ebay_action':         return 'Add';        
            case 'ebay_condition_id':   return '1000';       
            case 'ebay_format':         return 'FixedPrice'; 
            case 'ebay_duration':       return 'GTC';        
            case 'ebay_paypal_accepted':return '1';
            case 'ebay_country':
                
                if (function_exists('wc_get_base_location')) {
                    $loc = wc_get_base_location();
                    if (is_array($loc) && !empty($loc['country'])) {
                        return (string) $loc['country'];
                    }
                }
                return 'GR';
            case 'ebay_location':
            case 'ebay_category_id':
            case 'ebay_shipping_service':
                
                return '';
        }
        return '';
    }

    
    private $cached_currency_code   = null;
    
    private $cached_currency_symbol = null;

    
    private function scalar_or_empty($v) {
        if ($v === null || $v === '') { return ''; }
        $s = (string) $v;
        
        if (strpos($s, '.') !== false) {
            $s = rtrim(rtrim($s, '0'), '.');
            if ($s === '' || $s === '-') { return '0'; }
        }
        return $s;
    }

    
    private function query_product_ids(array $feed, $batch_size, $offset) {
        global $wpdb;

        $where = array("p.post_type = 'product'", "p.post_status = 'publish'");
        $params = array();

        if (!empty($feed['include_ids'])) {
            $placeholders = implode(',', array_fill(0, count($feed['include_ids']), '%d'));
            $where[] = "p.ID IN ({$placeholders})";
            $params = array_merge($params, array_map('intval', $feed['include_ids']));
        }
        if (!empty($feed['exclude_ids'])) {
            $placeholders = implode(',', array_fill(0, count($feed['exclude_ids']), '%d'));
            $where[] = "p.ID NOT IN ({$placeholders})";
            $params = array_merge($params, array_map('intval', $feed['exclude_ids']));
        }

        $join = '';
        if (!empty($feed['include_brand_ids']) || !empty($feed['exclude_brand_ids'])) {
            $join .= " LEFT JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID ";
            $join .= " LEFT JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id AND tt.taxonomy = 'product_brand' ";
        }
        if (!empty($feed['include_brand_ids'])) {
            $placeholders = implode(',', array_fill(0, count($feed['include_brand_ids']), '%d'));
            $where[] = "tt.term_id IN ({$placeholders})";
            $params = array_merge($params, array_map('intval', $feed['include_brand_ids']));
        }
        if (!empty($feed['exclude_brand_ids'])) {
            
            $placeholders = implode(',', array_fill(0, count($feed['exclude_brand_ids']), '%d'));
            $where[] = "p.ID NOT IN (
                SELECT tr2.object_id FROM {$wpdb->term_relationships} tr2
                INNER JOIN {$wpdb->term_taxonomy} tt2 ON tt2.term_taxonomy_id = tr2.term_taxonomy_id
                WHERE tt2.taxonomy = 'product_brand' AND tt2.term_id IN ({$placeholders})
            )";
            $params = array_merge($params, array_map('intval', $feed['exclude_brand_ids']));
        }

        $where_sql = 'WHERE ' . implode(' AND ', $where);
        
        $sql = "SELECT DISTINCT p.ID FROM {$wpdb->posts} p {$join} {$where_sql} ORDER BY p.ID ASC LIMIT %d OFFSET %d";
        $params[] = (int) $batch_size;
        $params[] = (int) $offset;

        $ids = $wpdb->get_col($wpdb->prepare($sql, $params));
        return array_map('intval', (array) $ids);
    }

    
    private function build_row($product, array $field_map) {
        $pid = (int) $product->get_id();
        $row = array();
        foreach ($field_map as $output_name => $source_key) {
            $row[$output_name] = $this->field_source($source_key, $product, $pid);
        }
        return $row;
    }

    private function field_source($key, $product, $pid) {
        switch ($key) {
            
            case 'product_id':  return (string) $pid;
            case 'sku':         return (string) $product->get_sku();
            case 'mpn':
                
                $mpn = (string) get_post_meta($pid, '_mpn', true);
                return $mpn !== '' ? $mpn : (string) $product->get_sku();
            case 'ean':
                
                foreach (array('_global_unique_id', '_ean', '_gtin', '_barcode') as $mk) {
                    $v = (string) get_post_meta($pid, $mk, true);
                    if ($v !== '') { return $v; }
                }
                return '';

            
            case 'name':        return (string) $product->get_name();
            case 'permalink':
                
                
                
                
                
                
                
                
                $parent = method_exists($product, 'get_parent_id') ? (int) $product->get_parent_id() : 0;
                return (string) get_permalink($parent > 0 ? $parent : $pid);
            case 'image_url':
                $img = wp_get_attachment_image_url($product->get_image_id(), 'large');
                return $img ? (string) $img : '';
            case 'image_gallery_urls':
                $ids = (array) $product->get_gallery_image_ids();
                $urls = array();
                foreach ($ids as $iid) {
                    $u = wp_get_attachment_image_url((int) $iid, 'large');
                    if ($u) { $urls[] = (string) $u; }
                }
                return implode(',', $urls);
            case 'additional_image':
                
                
                
                $ids = (array) $product->get_gallery_image_ids();
                foreach ($ids as $iid) {
                    $u = wp_get_attachment_image_url((int) $iid, 'large');
                    if ($u) { return (string) $u; }
                }
                return '';

            
            case 'category':
                
                $terms = wp_get_object_terms($pid, 'product_cat', array('fields' => 'names'));
                return is_wp_error($terms) ? '' : implode('>', (array) $terms);
            case 'category_leaf':
                $terms = wp_get_object_terms($pid, 'product_cat', array('fields' => 'names'));
                if (is_wp_error($terms) || empty($terms)) { return ''; }
                return (string) end($terms);
            case 'top_category':
                $terms = wp_get_object_terms($pid, 'product_cat', array('fields' => 'names'));
                if (is_wp_error($terms) || empty($terms)) { return ''; }
                return (string) reset($terms);
            case 'tags':
                $terms = wp_get_object_terms($pid, 'product_tag', array('fields' => 'names'));
                return is_wp_error($terms) ? '' : implode(',', (array) $terms);

            
            case 'brand':
                $terms = wp_get_object_terms($pid, 'product_brand', array('fields' => 'names'));
                return is_wp_error($terms) ? '' : (is_array($terms) && !empty($terms) ? (string) $terms[0] : '');

            
            case 'price':
                if (class_exists('AIEO_DMM_Pricing_Engine')) {
                    return (string) AIEO_DMM_Pricing_Engine::instance()->get_effective_price($pid, 0, (float) $product->get_regular_price());
                }
                return (string) $product->get_price();
            case 'regular_price': return (string) $product->get_regular_price();
            case 'sale_price':    return (string) $product->get_sale_price();

            
            case 'stock':         return (string) ($product->get_stock_quantity() ?? ($product->is_in_stock() ? 1 : 0));
            case 'availability':  return $product->is_in_stock() ? 'in stock' : 'out of stock';
            case 'instock_yn':    return $product->is_in_stock() ? 'Y' : 'N';
            case 'availability_text':
                
                
                if (!$product->is_in_stock()) {
                    return (string) get_option('aieo_feed_availability_oos', __('Μη διαθέσιμο', 'ai-eshop-optimizer'));
                }
                return (string) get_option('aieo_feed_availability_instock', __('Παράδοση σε 1 - 3 ημέρες', 'ai-eshop-optimizer'));
            case 'shipping_lead_time':
                
                $v = (string) get_post_meta($pid, '_shipping_lead_time', true);
                if ($v !== '' && is_numeric($v)) { return $v; }
                return (string) get_option('aieo_feed_default_lead_time', '0');

            
            case 'weight': return (string) $product->get_weight();
            case 'length': return (string) $product->get_length();
            case 'width':  return (string) $product->get_width();
            case 'height': return (string) $product->get_height();
            case 'color':
                $terms = wp_get_object_terms($pid, 'pa_color', array('fields' => 'names'));
                if (!is_wp_error($terms) && !empty($terms)) { return (string) $terms[0]; }
                $v = (string) get_post_meta($pid, '_color', true);
                return $v;
            case 'size':
                $terms = wp_get_object_terms($pid, 'pa_size', array('fields' => 'names'));
                if (!is_wp_error($terms) && !empty($terms)) { return (string) $terms[0]; }
                $v = (string) get_post_meta($pid, '_size', true);
                return $v;

            
            case 'description':
                
                $sd = (string) $product->get_short_description();
                if ($sd !== '') { return wp_strip_all_tags($sd); }
                
                return mb_substr(wp_strip_all_tags((string) $product->get_description()), 0, 280);
            case 'description_long':
                return wp_strip_all_tags((string) $product->get_description());
            case 'description_html':
                $sd = (string) $product->get_short_description();
                if ($sd !== '') { return $sd; }
                return (string) $product->get_description();

            
            
            
            
            case 'category_names':
                $terms = wp_get_object_terms($pid, 'product_cat', array('fields' => 'names'));
                return is_wp_error($terms) ? '' : implode(',', (array) $terms);

            
            
            
            case 'condition_new':
                $c = (string) get_post_meta($pid, '_aieo_condition', true);
                return $c !== '' ? $c : 'new';
            case 'price_with_currency':
                $price = (float) $product->get_price();
                if ($price <= 0) { return ''; }
                $code = function_exists('get_woocommerce_currency') ? (string) get_woocommerce_currency() : 'EUR';
                return sprintf('%.2f %s', $price, $code);
            case 'sale_price_with_currency':
                $reg = (float) $product->get_regular_price();
                $sale = (float) $product->get_sale_price();
                if (!($sale > 0 && $reg > 0 && $sale < $reg)) { return ''; }
                $code = function_exists('get_woocommerce_currency') ? (string) get_woocommerce_currency() : 'EUR';
                return sprintf('%.2f %s', $sale, $code);
            case 'sale_price_effective_date':
                
                
                
                
                
                $from = (int) get_post_meta($pid, '_sale_price_dates_from', true);
                $to   = (int) get_post_meta($pid, '_sale_price_dates_to', true);
                if ($from <= 0 || $to <= 0) { return ''; }
                $tz = function_exists('wp_timezone') ? wp_timezone() : new DateTimeZone('UTC');
                try {
                    $a = (new DateTimeImmutable('@' . $from))->setTimezone($tz)->format('Y-m-d\TH:iP');
                    $b = (new DateTimeImmutable('@' . $to))->setTimezone($tz)->format('Y-m-d\TH:iP');
                } catch (Exception $e) { return ''; }
                return $a . '/' . $b;
            case 'identifier_exists':
                
                
                
                
                
                foreach (array('_global_unique_id', '_ean', '_gtin', '_barcode') as $mk) {
                    if ((string) get_post_meta($pid, $mk, true) !== '') { return ''; }
                }
                return 'no';
            case 'category_names_multi_path':
                
                
                
                $terms = wp_get_object_terms($pid, 'product_cat', array('fields' => 'all'));
                if (is_wp_error($terms) || empty($terms)) { return ''; }
                $paths = array();
                foreach ($terms as $term) {
                    $chain = array($term->name);
                    $cur = $term;
                    $guard = 0;
                    while ((int) $cur->parent > 0 && $guard < 12) {
                        $cur = get_term((int) $cur->parent, 'product_cat');
                        if (!$cur || is_wp_error($cur)) { break; }
                        array_unshift($chain, $cur->name);
                        $guard++;
                    }
                    for ($i = 1, $n = count($chain); $i <= $n; $i++) {
                        $paths[implode(' > ', array_slice($chain, 0, $i))] = true;
                    }
                }
                return implode(' , ', array_keys($paths));
            case 'custom_label_0_on_sale':
                
                
                
                return $product->is_on_sale() ? 'on-sale' : '';
            case 'shipping_weight_kg':
                $w = (float) $product->get_weight();
                return $w > 0 ? sprintf('%.3f kg', $w) : '';
            case 'google_product_category':
                
                
                
                
                $mapped = $this->resolve_marketplace_category_for_product($pid, 'google');
                if ($mapped !== '') { return $mapped; }
                $v = (string) get_post_meta($pid, '_aieo_google_product_category', true);
                if ($v !== '') { return $v; }
                return (string) get_option('aieo_feed_default_google_product_category', '');
            case 'facebook_product_category':
                $mapped = $this->resolve_marketplace_category_for_product($pid, 'facebook');
                if ($mapped !== '') { return $mapped; }
                $v = (string) get_post_meta($pid, '_aieo_facebook_product_category', true);
                if ($v !== '') { return $v; }
                return (string) get_option('aieo_feed_default_facebook_product_category', '');
            case 'item_group_id':
                if (method_exists($product, 'get_parent_id')) {
                    $parent = (int) $product->get_parent_id();
                    if ($parent > 0 && $parent !== $pid) { return (string) $parent; }
                }
                return '';
            case 'custom_label_0':
                return (string) get_post_meta($pid, '_aieo_custom_label_0', true);
            case 'custom_label_1':
                return (string) get_post_meta($pid, '_aieo_custom_label_1', true);

            
            
            case 'availability':
                return $product->is_in_stock() ? 'in stock' : 'out of stock';

            
            
            
            case 'amazon_product_id_type':  return '4';   
            case 'amazon_condition_code':   return '11';  
            case 'amazon_add_action':       return 'a';   
            case 'amazon_ship_intl':
            case 'amazon_expedited':        return '';

            
            
            
            case 'ebay_action':         return 'Add';
            case 'ebay_condition_id':   return '1000';
            case 'ebay_format':         return 'FixedPrice';
            case 'ebay_duration':       return 'GTC';
            case 'ebay_paypal_accepted':return '1';
            case 'ebay_country':
                if (function_exists('wc_get_base_location')) {
                    $loc = wc_get_base_location();
                    if (is_array($loc) && !empty($loc['country'])) {
                        return (string) $loc['country'];
                    }
                }
                return 'GR';
            case 'ebay_location':
            case 'ebay_category_id':
            case 'ebay_shipping_service':
                return '';

            
            case 'currency_code':
                return function_exists('get_woocommerce_currency') ? (string) get_woocommerce_currency() : 'EUR';
            case 'currency_symbol':
                $code = function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'EUR';
                return function_exists('get_woocommerce_currency_symbol')
                    ? html_entity_decode((string) get_woocommerce_currency_symbol($code), ENT_QUOTES, 'UTF-8')
                    : '€';
        }
        return '';
    }

    
    private function resolve_marketplace_category_for_product($pid, $source) {
        global $wpdb;
        $pid = (int) $pid;
        if ($pid <= 0) { return ''; }
        $col = $source === 'facebook' ? 'facebook_category_path' : 'google_category_path';
        $ce  = $wpdb->prefix . 'aieo_dmm_category_extras';
        
        
        if ($wpdb->get_var("SHOW TABLES LIKE '{$ce}'") !== $ce) { return ''; }
        
        
        
        
        $sql = "SELECT ce.{$col} AS path, tt.parent AS parent
                FROM {$wpdb->term_relationships} tr
                INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
                INNER JOIN {$ce} ce ON ce.taxonomy = 'product_cat' AND ce.term_id = tt.term_id
                WHERE tr.object_id = %d
                  AND tt.taxonomy = 'product_cat'
                  AND ce.{$col} IS NOT NULL
                  AND ce.{$col} <> ''
                ORDER BY tt.parent DESC
                LIMIT 1";
        $val = $wpdb->get_var($wpdb->prepare($sql, $pid));
        return (string) ($val ?? '');
    }

    private function parse_int_list($raw) {
        $raw = (string) $raw;
        if ($raw === '') {
            return array();
        }
        $parts = preg_split('/[\s,;]+/', $raw, -1, PREG_SPLIT_NO_EMPTY);
        $out = array();
        foreach ($parts as $p) {
            $v = absint($p);
            if ($v > 0) {
                $out[] = $v;
            }
        }
        return array_values(array_unique($out));
    }

    
    private function collect_int_param($field) {
        if (isset($_POST[$field]) && is_array($_POST[$field])) {
            $vals = array_map('absint', wp_unslash($_POST[$field]));
            return array_values(array_unique(array_filter($vals)));
        }
        if (isset($_POST[$field]) && is_string($_POST[$field])) {
            return $this->parse_int_list(sanitize_text_field(wp_unslash($_POST[$field])));
        }
        return array();
    }

    
    private function collect_brand_param($field) {
        $raw = '';
        if (isset($_POST[$field])) {
            if (is_array($_POST[$field])) {
                $raw = implode(',', array_map('strval', wp_unslash($_POST[$field])));
            } else {
                $raw = (string) wp_unslash($_POST[$field]);
            }
        }
        $raw = trim($raw);
        if ($raw === '') { return array(); }

        $tokens = preg_split('/[\s,;]+/', $raw, -1, PREG_SPLIT_NO_EMPTY);
        $ids   = array();
        $slugs = array();
        foreach ($tokens as $t) {
            $t = trim((string) $t);
            if ($t === '') { continue; }
            if (ctype_digit($t)) {
                $ids[] = (int) $t;
            } else {
                
                $slugs[] = sanitize_title($t);
            }
        }
        if (!empty($slugs)) {
            $terms = get_terms(array(
                'taxonomy'   => 'product_brand',
                'slug'       => array_values(array_unique($slugs)),
                'hide_empty' => false,
                'fields'     => 'ids',
            ));
            if (!is_wp_error($terms) && is_array($terms)) {
                foreach ($terms as $tid) { $ids[] = (int) $tid; }
            }
        }
        return array_values(array_unique(array_filter($ids)));
    }
}
