<?php
if ( ! defined( 'ABSPATH' ) ) { exit; }

/**
 * Handles AJAX requests related to Rule Management.
 */
class KNSDD_Ajax_Rules {

    public function __construct() {
        add_action( 'wp_ajax_knsdd_save_rules', [ $this, 'save_rules' ] );
        add_action( 'wp_ajax_knsdd_get_product_categories', [ $this, 'get_product_categories' ] );
    }

    private function invalidate_cache() {
        update_option( 'knsdd_cache_version', time() );
        
        // Kategori hiyerarşi önbelleklerini temizle
        global $wpdb;
        $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_knsdd_rule_cat_exp_%' OR option_name LIKE '_transient_timeout_knsdd_rule_cat_exp_%'" );
        $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_knsdd_cat_bidir_%' OR option_name LIKE '_transient_timeout_knsdd_cat_bidir_%'" );
        $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_knsdd_prod_cat_exp_%' OR option_name LIKE '_transient_timeout_knsdd_prod_cat_exp_%'" );
    }

    /**
     * Mesaj metinleri için özel sanitizasyon:
     * - HTML taglerini kaldırır (wp_kses ile boş allowlist)
     * - Kontrol karakterlerini temizler
     * - % gibi karakterleri KORUR
     */
    private function sanitize_message_text( $text ) {
        $text = (string) $text;
        $text = wp_unslash( $text );
        $text = wp_kses( $text, array() ); // Tüm HTML'i kaldır fakat % vb. kalsın
        // Kontrol karakterlerini sil (tab, yeni satır hariç tamamen düz metin istenirse \t\n\r de temizlenebilir)
        $text = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $text );
        return trim( $text );
    }

    private function check_permissions() {
        check_ajax_referer( 'knsdd_admin_nonce', 'nonce' );
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( [ 'message' => __( 'Yetkiniz yok.', 'kns-dynamic-discounts-for-woocommerce' ) ], 403 );
        }
    }

    public function save_rules() {
        $this->check_permissions();
        $group = isset( $_POST['group'] ) ? trim( wp_unslash( $_POST['group'] ) ) : '';
        $rules = isset( $_POST['rules'] ) ? (array) $_POST['rules'] : [];
        if ( empty( $group ) ) { wp_send_json_error( [ 'message' => __( 'Grup seçilmedi.', 'kns-dynamic-discounts-for-woocommerce' ) ] ); }

        // Grup rule_type kontrolü
        $group_rule_type = KNSDD_Data::get_group_rule_type( $group );
        
        // fixed_discount gruplari icin kural limiti kontrolu
        if ( $group_rule_type === 'fixed_discount' ) {
            $limit_check = KNSDD_License::check_rules_limit( $group );
            // Yeni kuralların sayısını kontrol et
            if ( count( $rules ) > $limit_check['max'] ) {
                wp_send_json_error( array(
                    'message'     => $limit_check['message'],
                    'upgrade_url' => KNSDD_License::get_upgrade_url( 'rules' ),
                    'is_limit'    => true,
                ) );
            }
        }
        
        // Kural sayısı kontrolü (özel kampanya için)
        if ( $group_rule_type === 'special_campaign' && count( $rules ) > 1 ) {
            wp_send_json_error( [ 'message' => __( 'Özel kampanya gruplarında en fazla 1 kural olabilir.', 'kns-dynamic-discounts-for-woocommerce' ) ] );
        }

        $existing_rules_map = [];
        foreach (KNSDD_Data::get_rules_for_group($group) as $r) {
            if (isset($r['rule_id'])) {
                $existing_rules_map[$r['rule_id']] = $r;
            }
        }
        
        $sanitized_rules = [];
        foreach ( $rules as $rule ) {
            // Kampanya türünü önce kontrol et (BOGO mesaj sanitizasyonu için gerekli)
            $rule_type = isset( $rule['rule_type'] ) ? sanitize_text_field( $rule['rule_type'] ) : 'standard_discount';
            $campaign_type = ( $rule_type === 'special_campaign' && isset( $rule['campaign_type'] ) ) 
                ? sanitize_text_field( $rule['campaign_type'] ) 
                : '';
            $is_bogo = ( $campaign_type === 'buy_x_get_y_discount' );
            
            // Grup tipine göre kural tipi kontrolü
            $is_special = ( $rule_type === 'special_campaign' );
            
            if ( $group_rule_type === 'fixed_discount' && $is_special ) {
                wp_send_json_error( [ 'message' => __( 'Bu grup sadece Sabit İndirim (Miktar İndirimi) kabul eder.', 'kns-dynamic-discounts-for-woocommerce' ) ] );
            }
            
            if ( $group_rule_type === 'special_campaign' && ! $is_special ) {
                wp_send_json_error( [ 'message' => __( 'Bu grup sadece Özel Kampanya kabul eder.', 'kns-dynamic-discounts-for-woocommerce' ) ] );
            }
            
            $messages_data = isset($rule['messages']) ? (array) $rule['messages'] : [];
            $product_messages = isset($messages_data['product']) ? (array) $messages_data['product'] : [];
            $cart_messages = isset($messages_data['cart']) ? (array) $messages_data['cart'] : [];

            // Styles kaldırıldı (geri uyumluluk için mevcut verileri yok say)
            $sanitize_styles = function($arr) {
                return [];
            };
            // Icon sanitize helper (Iconify Tabler beyaz listesi + preset geri uyumluluk)
            $sanitize_icon = function($arr) {
                $arr = (array) $arr;
                $enabled  = isset($arr['enabled']) && filter_var($arr['enabled'], FILTER_VALIDATE_BOOLEAN);
                $position = isset($arr['position']) && in_array($arr['position'], ['before','after'], true) ? $arr['position'] : 'before';
                $size     = isset($arr['size']) && in_array($arr['size'], ['sm','md','lg'], true) ? $arr['size'] : 'md';
                $color    = isset($arr['color']) ? sanitize_hex_color( $arr['color'] ) : '';

                $pack_in  = isset($arr['pack']) ? sanitize_text_field($arr['pack']) : 'preset';
                $value_in = isset($arr['value']) ? (string) $arr['value'] : '';

                // Iconify (tabler) desteği: sadece belirli anahtarları kabul et
                if ( $pack_in === 'iconify' ) {
                    $allowed_tabler = [
                        'mail','bell-heart','balloon','confetti','discount-2','gift','gift-card',
                        'shopping-cart','shopping-bag','shopping-bag-check','shopping-bag-heart',
                        'shopping-cart-heart','shopping-cart-star','basket','basket-check','package',
                        'ticket','bell-ringing','tag','speakerphone','star','stars','rosette-discount-check',
                        'heart','check'
                    ];
                    // Beklenen format: tabler:<name>
                    if ( preg_match('/^tabler:([a-z0-9\-]+)$/', $value_in, $m) ) {
                        $name = $m[1];
                        if ( in_array( $name, $allowed_tabler, true ) ) {
                            return [
                                'enabled'  => $enabled,
                                'pack'     => 'iconify',
                                'value'    => 'tabler:' . $name,
                                'color'    => $color,
                                'position' => $position,
                                'size'     => $size,
                            ];
                        }
                    }
                    // Geçersizse güvenli varsayılan (tabler:star)
                    return [
                        'enabled'  => $enabled,
                        'pack'     => 'iconify',
                        'value'    => 'tabler:star',
                        'color'    => $color,
                        'position' => $position,
                        'size'     => $size,
                    ];
                }

                // Geri uyumluluk: preset SVG seti
                $value_key = sanitize_key( $value_in ); // a-z0-9-_
                $value     = $value_key ?: 'check';
                return [
                    'enabled'  => $enabled,
                    'pack'     => 'preset',
                    'value'    => $value,
                    'color'    => $color,
                    'position' => $position,
                    'size'     => $size,
                ];
            };

            $sanitized_messages = [
                'product' => [
                    // % işaretini koruyan özel sanitizasyon
                    'text'      => isset($product_messages['text']) ? $this->sanitize_message_text( $product_messages['text'] ) : '',
                    'color'     => isset($product_messages['color']) ? sanitize_hex_color( $product_messages['color'] ) : '#000000',
                    'show'      => isset($product_messages['show']) && filter_var($product_messages['show'], FILTER_VALIDATE_BOOLEAN),
                    'highlight' => isset($product_messages['highlight']) && filter_var($product_messages['highlight'], FILTER_VALIDATE_BOOLEAN),
                    // styles kaldırıldı; eski veriler varsa yok sayılır
                    'styles'    => [],
                    'icon'      => $sanitize_icon( isset($product_messages['icon']) ? $product_messages['icon'] : [] ),
                ],
                'cart' => [
                    'text'            => isset($cart_messages['text']) ? $this->sanitize_message_text( $cart_messages['text'] ) : '',
                    'color'           => isset($cart_messages['color']) ? sanitize_hex_color( $cart_messages['color'] ) : '#000000',
                    'show_mini'       => isset($cart_messages['show_mini']) && filter_var($cart_messages['show_mini'], FILTER_VALIDATE_BOOLEAN),
                    'show_cart'       => isset($cart_messages['show_cart']) && filter_var($cart_messages['show_cart'], FILTER_VALIDATE_BOOLEAN),
                    'show_checkout'   => isset($cart_messages['show_checkout']) && filter_var($cart_messages['show_checkout'], FILTER_VALIDATE_BOOLEAN),
                    // Tekil vurgulama: highlight
                    'highlight'       => (
                        ( isset($cart_messages['highlight']) && filter_var($cart_messages['highlight'], FILTER_VALIDATE_BOOLEAN) ) 
                        || ( isset($cart_messages['highlight_mini']) && filter_var($cart_messages['highlight_mini'], FILTER_VALIDATE_BOOLEAN) ) 
                        || ( isset($cart_messages['highlight_cart']) && filter_var($cart_messages['highlight_cart'], FILTER_VALIDATE_BOOLEAN) ) 
                        || ( isset($cart_messages['highlight_checkout']) && filter_var($cart_messages['highlight_checkout'], FILTER_VALIDATE_BOOLEAN) )
                    ),
                    // styles kaldırıldı; eski veriler varsa yok sayılır
                    'styles'          => [],
                    'icon'            => $sanitize_icon( isset($cart_messages['icon']) ? $cart_messages['icon'] : [] ),
                ]
            ];
            
            // BOGO için ek mesaj alanları
            if ( $is_bogo ) {
                $product_trigger_messages = isset($messages_data['product_trigger']) ? (array) $messages_data['product_trigger'] : [];
                $product_target_messages = isset($messages_data['product_target']) ? (array) $messages_data['product_target'] : [];
                
                $sanitized_messages['product_trigger'] = [
                    'text'      => isset($product_trigger_messages['text']) ? $this->sanitize_message_text( $product_trigger_messages['text'] ) : '',
                    'color'     => isset($product_trigger_messages['color']) ? sanitize_hex_color( $product_trigger_messages['color'] ) : '#000000',
                    'show'      => isset($product_trigger_messages['show']) && filter_var($product_trigger_messages['show'], FILTER_VALIDATE_BOOLEAN),
                    'highlight' => isset($product_trigger_messages['highlight']) && filter_var($product_trigger_messages['highlight'], FILTER_VALIDATE_BOOLEAN),
                    'styles'    => [],
                    'icon'      => $sanitize_icon( isset($product_trigger_messages['icon']) ? $product_trigger_messages['icon'] : [] ),
                ];
                
                $sanitized_messages['product_target'] = [
                    'text'      => isset($product_target_messages['text']) ? $this->sanitize_message_text( $product_target_messages['text'] ) : '',
                    'color'     => isset($product_target_messages['color']) ? sanitize_hex_color( $product_target_messages['color'] ) : '#000000',
                    'show'      => isset($product_target_messages['show']) && filter_var($product_target_messages['show'], FILTER_VALIDATE_BOOLEAN),
                    'highlight' => isset($product_target_messages['highlight']) && filter_var($product_target_messages['highlight'], FILTER_VALIDATE_BOOLEAN),
                    'styles'    => [],
                    'icon'      => $sanitize_icon( isset($product_target_messages['icon']) ? $product_target_messages['icon'] : [] ),
                ];
            }

            $product_ids = ( ! empty( $rule['product'] ) && is_array( $rule['product'] ) ) ? array_map( 'absint', $rule['product'] ) : [];

            $rule_id = empty( $rule['rule_id'] ) ? uniqid( 'knsdd_' ) : sanitize_text_field($rule['rule_id']);
            $existing_rule = $existing_rules_map[$rule_id] ?? [];

            // Normalize first_order_only to a strict boolean (accepts 'true'/'false', 1/0, etc.)
            $first_order_only = false;
            if ( isset( $rule['first_order_only'] ) ) {
                $first_order_only = filter_var( $rule['first_order_only'], FILTER_VALIDATE_BOOLEAN );
            } elseif ( isset( $rule['ext'] ) && is_array( $rule['ext'] ) && isset( $rule['ext']['first_order_only'] ) ) {
                $first_order_only = filter_var( $rule['ext']['first_order_only'], FILTER_VALIDATE_BOOLEAN );
            }

            // Kategori: Tek değer veya array olabilir, array olarak normalize et
            $category_raw = isset( $rule['category'] ) ? $rule['category'] : null;
            $category_normalized = null;
            if ( ! empty( $category_raw ) ) {
                if ( is_array( $category_raw ) ) {
                    $category_normalized = array_filter( array_map( 'absint', $category_raw ) );
                } else {
                    $cat_id = absint( $category_raw );
                    $category_normalized = $cat_id > 0 ? [ $cat_id ] : null;
                }
            }
            
            $base_rule = [
                'rule_id'           => $rule_id,
                'category'          => $category_normalized,
                'product'           => $product_ids,
                'min'               => isset( $rule['min'] ) ? absint( $rule['min'] ) : 0,
                'max'               => isset( $rule['max'] ) ? absint( $rule['max'] ) : 0,
                'messages'          => $sanitized_messages,
                'login_required'    => isset( $rule['login_required'] ) && filter_var( $rule['login_required'], FILTER_VALIDATE_BOOLEAN ),
                'first_order_only'  => $first_order_only,
                'allowed_customers' => ( ! empty( $rule['allowed_customers'] ) && is_array( $rule['allowed_customers'] ) ) ? array_map( 'absint', $rule['allowed_customers'] ) : [],
                'total_limit'       => isset( $rule['total_limit'] ) ? absint( $rule['total_limit'] ) : ( $existing_rule['total_limit'] ?? 0 ),
                'per_customer_limit'=> isset( $rule['per_customer_limit'] ) ? absint( $rule['per_customer_limit'] ) : ( $existing_rule['per_customer_limit'] ?? 0 ),
                'rule_type'         => isset( $rule['rule_type'] ) ? sanitize_text_field( $rule['rule_type'] ) : 'standard_discount',
            ];

            // Server-side normalization: If both limits > 0, per_customer_limit cannot exceed total_limit
            if ( $base_rule['total_limit'] > 0 && $base_rule['per_customer_limit'] > 0 && $base_rule['per_customer_limit'] > $base_rule['total_limit'] ) {
                $base_rule['per_customer_limit'] = $base_rule['total_limit'];
            }

            if ( $base_rule['rule_type'] === 'special_campaign' ) {
                $base_rule['campaign_type'] = isset( $rule['campaign_type'] ) ? sanitize_text_field( $rule['campaign_type'] ) : '';
                if ( $base_rule['campaign_type'] === 'buy_x_pay_y' ) {
                    $base_rule['buy_x'] = isset( $rule['buy_x'] ) ? absint( $rule['buy_x'] ) : 0;
                    $base_rule['pay_y'] = isset( $rule['pay_y'] ) ? absint( $rule['pay_y'] ) : 0;
                } elseif ( $base_rule['campaign_type'] === 'nth_percent_off' ) {
                    $nth = isset( $rule['nth'] ) ? absint( $rule['nth'] ) : 0;
                    $incoming_type  = isset( $rule['discount_type'] ) ? sanitize_text_field( $rule['discount_type'] ) : '';
                    $incoming_value = isset( $rule['discount_value'] ) ? floatval( $rule['discount_value'] ) : 0.0;
                    $discount_type  = in_array( $incoming_type, [ 'amount', 'percentage' ], true ) ? $incoming_type : '';
                    $discount_value = $incoming_value;

                    // Server-side validation
                    if ( $nth < 1 ) { continue; }
                    if ( $discount_type === 'percentage' ) {
                        if ( $discount_value <= 0 || $discount_value >= 100 ) { continue; }
                    } elseif ( $discount_type === 'amount' ) {
                        if ( $discount_value <= 0 ) { continue; }
                    } else {
                        continue; // invalid type
                    }

                    $base_rule['nth']            = $nth;
                    $base_rule['discount_type']  = $discount_type;
                    $base_rule['discount_value'] = $discount_value;
                } elseif ( $base_rule['campaign_type'] === 'buy_x_get_y_discount' ) {
                    // Trigger
                    $trigger_type = isset( $rule['trigger_type'] ) ? sanitize_text_field( $rule['trigger_type'] ) : 'product';
                    $trigger_type = in_array( $trigger_type, [ 'product', 'category' ], true ) ? $trigger_type : 'product';
                    $trigger_quantity = isset( $rule['trigger_quantity'] ) ? absint( $rule['trigger_quantity'] ) : 1;
                    
                    // Target
                    $target_type = isset( $rule['target_type'] ) ? sanitize_text_field( $rule['target_type'] ) : 'product';
                    $target_type = in_array( $target_type, [ 'product', 'category' ], true ) ? $target_type : 'product';
                    $target_quantity = isset( $rule['target_quantity'] ) ? absint( $rule['target_quantity'] ) : 1;
                    
                    // Discount
                    $discount_type = isset( $rule['discount_type'] ) ? sanitize_text_field( $rule['discount_type'] ) : 'percentage';
                    $discount_type = in_array( $discount_type, [ 'percentage', 'amount', 'free' ], true ) ? $discount_type : 'percentage';
                    $discount_value = isset( $rule['discount_value'] ) ? floatval( $rule['discount_value'] ) : 0;
                    
                    // Server-side validation
                    if ( $trigger_quantity < 1 || $target_quantity < 1 ) {
                        continue;
                    }
                    
                    if ( $discount_type === 'percentage' && ( $discount_value <= 0 || $discount_value > 100 ) ) {
                        continue;
                    }
                    
                    if ( $discount_type === 'amount' && $discount_value <= 0 ) {
                        continue;
                    }
                    
                    // Trigger data - YENİ MANTIK: Hem kategori hem ürün desteklenir
                    $base_rule['trigger_type'] = $trigger_type;
                    $base_rule['trigger_quantity'] = $trigger_quantity;
                    
                    $trigger_products = isset( $rule['trigger_products'] ) && is_array( $rule['trigger_products'] ) 
                        ? array_filter( array_map( 'absint', $rule['trigger_products'] ) ) 
                        : [];
                    $trigger_category = isset( $rule['trigger_category'] ) ? absint( $rule['trigger_category'] ) : 0;
                    
                    // Validasyon: En az biri seçilmiş olmalı
                    if ( empty( $trigger_products ) && $trigger_category <= 0 ) {
                        continue; // Ne ürün ne kategori seçilmemiş
                    }
                    
                    // Kategori + Ürün kombinasyonu: Ürünlerin kategoriye ait olduğunu doğrula
                    // Hiyerarşik destek: Üst kategori seçiliyse alt kategorilerdeki ürünler de geçerli
                    if ( $trigger_category > 0 && ! empty( $trigger_products ) ) {
                        $valid_products = [];
                        foreach ( $trigger_products as $pid ) {
                            $product_cats = wc_get_product_term_ids( $pid, 'product_cat' );
                            // Varyasyon için parent kategorilerini kontrol et
                            if ( empty( $product_cats ) ) {
                                $parent_id = wp_get_post_parent_id( $pid );
                                if ( $parent_id > 0 ) {
                                    $product_cats = wc_get_product_term_ids( $parent_id, 'product_cat' );
                                }
                            }
                            
                            // Hiyerarşik kategori kontrolü: Ürün kategorilerini genişlet (alt → üst)
                            $expanded_product_cats = array_map('intval', $product_cats);
                            foreach ($product_cats as $cat_id) {
                                $ancestors = get_ancestors((int)$cat_id, 'product_cat', 'taxonomy');
                                if (!empty($ancestors)) {
                                    $expanded_product_cats = array_merge($expanded_product_cats, array_map('intval', $ancestors));
                                }
                            }
                            $expanded_product_cats = array_unique($expanded_product_cats);
                            
                            // Seçilen kategori genişletilmiş listede var mı?
                            if ( in_array( $trigger_category, $expanded_product_cats, true ) ) {
                                $valid_products[] = $pid;
                            }
                        }
                        if ( empty( $valid_products ) ) {
                            continue; // Hiçbir ürün seçili kategoriye ait değil
                        }
                        $trigger_products = $valid_products;
                    }
                    
                    $base_rule['trigger_products'] = $trigger_products;
                    $base_rule['trigger_category'] = $trigger_category;
                    
                    // Target data - YENİ MANTIK: Hem kategori hem ürün desteklenir
                    $base_rule['target_type'] = $target_type;
                    $base_rule['target_quantity'] = $target_quantity;
                    
                    $target_products = isset( $rule['target_products'] ) && is_array( $rule['target_products'] ) 
                        ? array_filter( array_map( 'absint', $rule['target_products'] ) ) 
                        : [];
                    $target_category = isset( $rule['target_category'] ) ? absint( $rule['target_category'] ) : 0;
                    
                    // Validasyon: En az biri seçilmiş olmalı
                    if ( empty( $target_products ) && $target_category <= 0 ) {
                        continue; // Ne ürün ne kategori seçilmemiş
                    }
                    
                    // Kategori + Ürün kombinasyonu: Ürünlerin kategoriye ait olduğunu doğrula
                    // Hiyerarşik destek: Üst kategori seçiliyse alt kategorilerdeki ürünler de geçerli
                    if ( $target_category > 0 && ! empty( $target_products ) ) {
                        $valid_products = [];
                        foreach ( $target_products as $pid ) {
                            $product_cats = wc_get_product_term_ids( $pid, 'product_cat' );
                            // Varyasyon için parent kategorilerini kontrol et
                            if ( empty( $product_cats ) ) {
                                $parent_id = wp_get_post_parent_id( $pid );
                                if ( $parent_id > 0 ) {
                                    $product_cats = wc_get_product_term_ids( $parent_id, 'product_cat' );
                                }
                            }
                            
                            // Hiyerarşik kategori kontrolü: Ürün kategorilerini genişlet (alt → üst)
                            $expanded_product_cats = array_map('intval', $product_cats);
                            foreach ($product_cats as $cat_id) {
                                $ancestors = get_ancestors((int)$cat_id, 'product_cat', 'taxonomy');
                                if (!empty($ancestors)) {
                                    $expanded_product_cats = array_merge($expanded_product_cats, array_map('intval', $ancestors));
                                }
                            }
                            $expanded_product_cats = array_unique($expanded_product_cats);
                            
                            // Seçilen kategori genişletilmiş listede var mı?
                            if ( in_array( $target_category, $expanded_product_cats, true ) ) {
                                $valid_products[] = $pid;
                            }
                        }
                        if ( empty( $valid_products ) ) {
                            continue; // Hiçbir ürün seçili kategoriye ait değil
                        }
                        $target_products = $valid_products;
                    }
                    
                    $base_rule['target_products'] = $target_products;
                    $base_rule['target_category'] = $target_category;
                    
                    // Ek Sunucu Tarafı Doğrulama & Otomatik Temizleme:
                    // 1) Tetikleyici ve hedef ürün setleri birebir aynı ise BLOKLA
                    // 2) Aksi halde, kesişen ürünleri hedeften çıkararak otomatik temizle
                    if ( ! empty( $trigger_products ) && ! empty( $target_products ) ) {
                        $tp = array_values( array_unique( array_map( 'intval', $trigger_products ) ) );
                        $gp = array_values( array_unique( array_map( 'intval', $target_products ) ) );
                        sort( $tp ); sort( $gp );
                        $intersection = array_values( array_intersect( $tp, $gp ) );
                        if ( ! empty( $intersection ) ) {
                            if ( $tp === $gp ) {
                                // Tamamen aynı set: sistemsel hata – kaydı engelle
                                wp_send_json_error( [ 'message' => __( 'Tetikleyici ve hedef ürün seçimleri birebir aynı olamaz. Lütfen farklı ürün(ler) seçin.', 'kns-dynamic-discounts-for-woocommerce' ) ] );
                            }
                            // Kısmi kesişim: hedeften çıkar
                            $gp_pruned = array_values( array_diff( $gp, $intersection ) );
                            $target_products = $gp_pruned;
                            $base_rule['target_products'] = $target_products;
                        }
                    }

                    $base_rule['discount_type'] = $discount_type;
                    $base_rule['discount_value'] = $discount_value;
                    
                    // Bu kampanya türünde ana category ve product alanları kullanılmaz
                    $base_rule['category'] = null;
                    $base_rule['product'] = [];
                } elseif ( $base_rule['campaign_type'] === 'cart_total_discount' ) {
                    $min_cart_total = isset( $rule['min_cart_total'] ) ? floatval( $rule['min_cart_total'] ) : 0;
                    $discount_value = isset( $rule['discount_value'] ) ? floatval( $rule['discount_value'] ) : 0;
                    $discount_type  = isset( $rule['discount_type'] ) && in_array( $rule['discount_type'], [ 'amount', 'percentage' ] ) ? $rule['discount_type'] : 'amount';

                    // Sunucu tarafı doğrulama
                    if ($min_cart_total <= 0 || $discount_value <= 0) {
                        continue; // Bu kuralı atla
                    }
                    if ($discount_type === 'amount' && $discount_value >= $min_cart_total) {
                        continue; // Bu kuralı atla
                    }
                    if ($discount_type === 'percentage' && $discount_value > 100) {
                        continue; // Bu kuralı atla
                    }

                    $base_rule['min_cart_total'] = $min_cart_total;
                    $base_rule['discount_type']  = $discount_type;
                    $base_rule['discount_value'] = $discount_value;
                    // Bu kampanya türünde kategori ve ürün seçimi olmaz.
                    $base_rule['category'] = null;
                    $base_rule['product'] = [];
                }
                $base_rule['min'] = 0;
                $base_rule['max'] = 0;
            } else {
                $base_rule['discount_type'] = isset( $rule['discount_type'] ) && 'fixed' === $rule['discount_type'] ? 'fixed' : 'percent';
                $base_rule['discount_value'] = isset( $rule['discount_value'] ) ? floatval( $rule['discount_value'] ) : 0;
            }
            
            // Normalize min/max for standard rules (max=0 means infinite and should not trigger swap)
            if ( $base_rule['rule_type'] !== 'special_campaign' ) {
                $mn = isset($base_rule['min']) ? (int)$base_rule['min'] : 0;
                $mx = isset($base_rule['max']) ? (int)$base_rule['max'] : 0;
                if ( $mx > 0 && $mn > $mx ) {
                    $tmp = $mn; $mn = $mx; $mx = $tmp;
                }
                $base_rule['min'] = max(0, $mn);
                $base_rule['max'] = max(0, $mx);
            }

            // ext support: bilinmeyen alanları güvenli şekilde topla ve sakla
            $known_keys = [
                'rule_id','category','product','min','max','messages','login_required','first_order_only','allowed_customers','total_limit','per_customer_limit','rule_type',
                'campaign_type','buy_x','pay_y','nth','min_cart_total','discount_value','discount_type','ext'
            ];
            $extra = array_diff_key( (array) $rule, array_flip( $known_keys ) );
            $input_ext = isset($rule['ext']) && is_array($rule['ext']) ? $rule['ext'] : [];
            // Ensure first_order_only is NOT stored inside ext anymore (backward compatibility cleanup)
            if ( isset( $input_ext['first_order_only'] ) ) { unset( $input_ext['first_order_only'] ); }
            $merged_ext = array_merge( $input_ext, $extra );
            $safe_ext = $this->sanitize_ext_value( $merged_ext, 0 );
            if ( is_array($safe_ext) && ! empty($safe_ext) ) {
                $base_rule['ext'] = $safe_ext;
            }
            
            $sanitized_rules[] = $base_rule;
        }

        // --- TİER GRUP LİMİT SENKRONIZASYONU (standard_discount için) ---
        // Aynı ürün/kategori kapsamındaki satırlar aynı limiti paylaşmalı
        $tier_groups = [];
        foreach ( $sanitized_rules as $idx => $entry ) {
            if ( $entry['rule_type'] !== 'standard_discount' ) {
                continue;
            }
            // Kapsam anahtarı: kategori + ürün seti (sıralı)
            $cat = isset( $entry['category'] ) ? absint( $entry['category'] ) : 0;
            $products = isset( $entry['product'] ) && is_array( $entry['product'] ) ? $entry['product'] : [];
            sort( $products ); // Sıralama önemli
            $key = $cat . '|' . implode( ',', $products );
            
            // Boş kapsam atla
            if ( ! $cat && empty( $products ) ) {
                continue;
            }
            
            if ( ! isset( $tier_groups[ $key ] ) ) {
                $tier_groups[ $key ] = [];
            }
            $tier_groups[ $key ][] = $idx;
        }
        
        // Her tier grubunda ilk satırın limitini tüm satırlara uygula
        foreach ( $tier_groups as $indices ) {
            if ( count( $indices ) > 1 ) {
                $first_idx = $indices[0];
                $shared_total = $sanitized_rules[ $first_idx ]['total_limit'];
                $shared_per_customer = $sanitized_rules[ $first_idx ]['per_customer_limit'];
                
                foreach ( $indices as $idx ) {
                    $sanitized_rules[ $idx ]['total_limit'] = $shared_total;
                    $sanitized_rules[ $idx ]['per_customer_limit'] = $shared_per_customer;
                }
            }
        }

        // --- AYNI GRUPTA AYNI KAPSAMDA MİKTAR ARALIĞI ÇAKIŞMASI (ÜRÜN–ÜRÜN ve KATEGORİ–KATEGORİ) ---
        // max=0 sonsuz kabul edilir; [a,b] ve [c,d] için a <= d && c <= b ise aralıklar çakışır
        $parse_range = function($min, $max) {
            $mn = is_numeric($min) ? floatval($min) : 0.0;
            $mx = is_numeric($max) ? floatval($max) : 0.0;
            if ($mn < 0) { $mn = 0.0; }
            if ($mx < 0) { $mx = 0.0; }
            $mx = ($mx == 0.0) ? INF : $mx;
            return [ 'min' => $mn, 'max' => $mx ];
        };
        $ranges_overlap_nums = function($A, $B) { return ($A['min'] <= $B['max']) && ($B['min'] <= $A['max']); };

        // --- VALIDATION: Use validator registry for intra-group validation ---
        $dates = KNSDD_Data::get_dates_for_group($group);
        $intra_validation = KNSDD_Validator_Registry::validate_intra_group( 
            $sanitized_rules, 
            'ui', 
            [ 'group_name' => $group, 'dates' => $dates ]
        );
        
        if ( ! $intra_validation['ok'] ) {
            $error_message = '';
            foreach ( $intra_validation['errors'] as $error ) {
                $error_message .= $error['message'] . ' ';
            }
            wp_send_json_error( [ 'message' => trim( $error_message ) ] );
        }

        // --- ÜRÜN–ÜRÜN TEKRARI: Yalnızca aralıklar çakışıyorsa engelle ---
        $seen_product_ranges = []; // productId => list of ranges
        $repeat_overlap_found = false;
        foreach ($sanitized_rules as $entry) {
            // cart_total_discount ve tamamen boş satırları kapsam dışı tut
            $type = isset($entry['rule_type']) ? $entry['rule_type'] : 'standard_discount';
            if ($type === 'special_campaign' && isset($entry['campaign_type']) && $entry['campaign_type'] === 'cart_total_discount') { continue; }
            $products = (isset($entry['product']) && is_array($entry['product'])) ? array_filter(array_map('absint', $entry['product'])) : [];
            $rng = $parse_range( isset($entry['min']) ? $entry['min'] : 0, isset($entry['max']) ? $entry['max'] : 0 );
            if (!empty($products)) { 
                foreach ($products as $pid) {
                    $k = (int)$pid; // varyasyonlar ayrı ID olarak kabul
                    $arr = isset($seen_product_ranges[$k]) ? $seen_product_ranges[$k] : [];
                    foreach ($arr as $prevRange) {
                        if ($ranges_overlap_nums($prevRange, $rng)) { $repeat_overlap_found = true; break 2; }
                    }
                    $arr[] = $rng;
                    $seen_product_ranges[$k] = $arr;
                }
            }
        }
        if ($repeat_overlap_found) {
            wp_send_json_error( [ 'message' => __( 'Aynı ürün için miktar aralıkları çakışan birden fazla kural var. Lütfen aralıkları ayrıştırın.', 'kns-dynamic-discounts-for-woocommerce' ) ] );
        }

        // --- SERVER-SIDE KAPSAM ÇAKIŞMASI DOĞRULAMA ---
        // 1) Aynı grup içinde: kategori-only ile ürünlerin kategorileri kesişiyor mu?
        // 2) Gruplar arası (tarih çakışıyorsa): bizim ürünler vs diğer kategori-only, bizim kategori-only vs diğer ürünler, ve kategori-kategori aynı

        // Yardımcı: bu kural satırı kontrol kapsamına girer mi?
        $is_effective_rule = function($r) {
            if (empty($r) || !is_array($r)) return false;
            $type = isset($r['rule_type']) ? $r['rule_type'] : 'standard_discount';
            if ($type === 'special_campaign' && isset($r['campaign_type']) && $r['campaign_type'] === 'cart_total_discount') return false;
            $cat = isset($r['category']) ? absint($r['category']) : 0;
            $products = (isset($r['product']) && is_array($r['product'])) ? array_filter(array_map('absint', $r['product'])) : [];
            return ($cat > 0) || (!empty($products));
        };

        // Mevcut grubun setleri
        $my_category_only = [];
        $my_product_ids   = [];
        $my_category_only_ranges = []; // catId => [ ['min'=>..,'max'=>..], ... ]
        foreach ($sanitized_rules as $r) {
            if (! $is_effective_rule($r)) { continue; }
            $cat = isset($r['category']) ? absint($r['category']) : 0;
            $products = (isset($r['product']) && is_array($r['product'])) ? array_filter(array_map('absint', $r['product'])) : [];
            if ($cat > 0 && empty($products)) {
                $my_category_only[] = $cat;
                $rng = $parse_range( isset($r['min']) ? $r['min'] : 0, isset($r['max']) ? $r['max'] : 0 );
                if (!isset($my_category_only_ranges[$cat])) { $my_category_only_ranges[$cat] = []; }
                $my_category_only_ranges[$cat][] = $rng;
            }
            if (!empty($products)) { $my_product_ids = array_merge($my_product_ids, $products); }
        }
        $my_category_only = array_values(array_unique($my_category_only));
        $my_product_ids   = array_values(array_unique($my_product_ids));

        // Ürün->kategori eşlemesi (sadece mevcut grubun ürünleri için; intra kontrol yeterli)
        $product_to_cats = [];
        if (!empty($my_product_ids)) {
            $terms = wp_get_object_terms( $my_product_ids, 'product_cat', [ 'fields' => 'all_with_object_id' ] );
            if (! is_wp_error($terms)) {
                foreach ($terms as $t) {
                    $pid = isset($t->object_id) ? (int)$t->object_id : 0;
                    $tid = isset($t->term_id) ? (int)$t->term_id : 0;
                    if ($pid && $tid) {
                        if (!isset($product_to_cats[$pid])) $product_to_cats[$pid] = [];
                        $product_to_cats[$pid][] = $tid;
                    }
                }
            }
            foreach ($my_product_ids as $pid) { if (!isset($product_to_cats[$pid])) $product_to_cats[$pid] = []; }

            // Fallback: Varyasyonlar için ebeveyn ürünün kategorilerini devral (parent product)
            $parent_map = [];
            $need_parents = [];
            foreach ($my_product_ids as $pid) {
                if (empty($product_to_cats[$pid])) {
                    $par = (int) wp_get_post_parent_id($pid);
                    if ($par > 0) { $parent_map[$pid] = $par; $need_parents[$par] = true; }
                }
            }
            if (!empty($need_parents)) {
                $parent_ids = array_keys($need_parents);
                $pterms = wp_get_object_terms( $parent_ids, 'product_cat', [ 'fields' => 'all_with_object_id' ] );
                $parent_cats = [];
                if (! is_wp_error($pterms)) {
                    foreach ($pterms as $t) {
                        $poid = isset($t->object_id) ? (int)$t->object_id : 0;
                        $tid  = isset($t->term_id) ? (int)$t->term_id : 0;
                        if ($poid && $tid) {
                            if (!isset($parent_cats[$poid])) { $parent_cats[$poid] = []; }
                            $parent_cats[$poid][] = $tid;
                        }
                    }
                }
                foreach ($parent_map as $child => $par) {
                    $product_to_cats[$child] = isset($parent_cats[$par]) ? $parent_cats[$par] : [];
                }
            }
        }

        $my_cat_set     = array_flip($my_category_only); // hızlı membership için

        // Row-level doğrulama: Hem kategori hem ürün(ler) seçiliyse, tüm ürünler kategoriye ait olmalı
        foreach ($sanitized_rules as $entry) {
            $type = isset($entry['rule_type']) ? $entry['rule_type'] : 'standard_discount';
            if ($type === 'special_campaign' && isset($entry['campaign_type']) && $entry['campaign_type'] === 'cart_total_discount') { continue; }
            
            // Kategori array veya tek değer olabilir
            $cat_raw = isset($entry['category']) ? $entry['category'] : null;
            $selected_cats = [];
            if (!empty($cat_raw)) {
                if (is_array($cat_raw)) {
                    $selected_cats = array_filter(array_map('absint', $cat_raw));
                } else {
                    $cat_id = absint($cat_raw);
                    if ($cat_id > 0) {
                        $selected_cats = [$cat_id];
                    }
                }
            }
            
            $products = (isset($entry['product']) && is_array($entry['product'])) ? array_filter(array_map('absint', $entry['product'])) : [];
            
            if (!empty($selected_cats) && !empty($products)) {
                foreach ($products as $pid) {
                    $cats = isset($product_to_cats[$pid]) ? (array)$product_to_cats[$pid] : [];
                    
                    // Hiyerarşik kategori kontrolü: Ürün kategorilerini genişlet (alt → üst)
                    $expanded_cats = array_map('intval', $cats);
                    foreach ($cats as $cat_id) {
                        $ancestors = get_ancestors((int)$cat_id, 'product_cat', 'taxonomy');
                        if (!empty($ancestors)) {
                            $expanded_cats = array_merge($expanded_cats, array_map('intval', $ancestors));
                        }
                    }
                    $expanded_cats = array_unique($expanded_cats);
                    
                    // Seçilen kategorilerden en az biri genişletilmiş listede var mı?
                    $belongs = !empty(array_intersect($selected_cats, $expanded_cats));
                    if (!$belongs) {
                        wp_send_json_error( [ 'message' => __( 'Seçili ürün(ler) seçilen kategoriye ait değil. Lütfen uyumlu seçim yapın veya kategoriyi kaldırın.', 'kns-dynamic-discounts-for-woocommerce' ) ] );
                    }
                }
            }
        }

        // 1) Aynı grupta: ürün kategorileri vs kendi kategori-only
        $intra_conflict = false;
        foreach ($my_product_ids as $pid) {
            $cats = isset($product_to_cats[$pid]) ? (array)$product_to_cats[$pid] : [];
            foreach ($cats as $c) { if (isset($my_cat_set[(int)$c])) { $intra_conflict = true; break 2; } }
        }

        if ($intra_conflict) {
            wp_send_json_error( [ 'message' => _x( 'Bu grupta aynı kategori için hem kategori düzeyinde hem de bu kategorideki bir ürüne yönelik kural tanımlı. Lütfen çakışmayı giderin.', 'ajax error response', 'kns-dynamic-discounts-for-woocommerce' ) ] );
        }

        // 2) Gruplar arası (tarih çakışıyorsa): ortak doğrulayıcı
        $my_dates = KNSDD_Data::get_dates_for_group($group);
        $start_d = isset($my_dates['start']) ? $my_dates['start'] : '';
        $end_d   = isset($my_dates['end']) ? $my_dates['end'] : '';
        $my_sets = $intra_validation['sets']; // Use sets from intra-group validation
        
        $result = KNSDD_Validator_Registry::validate_cross_group(
            $group, $start_d, $end_d, $my_sets, $sanitized_rules, [], 
            'ui', 
            [ 'group_name' => $group, 'dates' => $my_dates ]
        );
        
        if ( isset($result['conflict']) && $result['conflict'] ) {
            // Check if it's a campaign type mismatch
            if ( isset($result['type']) && $result['type'] === 'campaign_type_mismatch' ) {
                $conflict_with = isset($result['with']) ? $result['with'] : '';
                $msg = $conflict_with 
                    ? sprintf( __( 'Kampanya türü çakışması: "%s" grubu ile aynı ürün için farklı kampanya türleri tanımlı. Lütfen düzenleyin.', 'kns-dynamic-discounts-for-woocommerce' ), $conflict_with )
                    : __( 'Kampanya türü çakışması: Aynı ürün için farklı kampanya türleri tanımlı. Lütfen düzenleyin.', 'kns-dynamic-discounts-for-woocommerce' );
                wp_send_json_error( [ 'message' => $msg ] );
            } else {
                wp_send_json_error( [ 'message' => __( 'Gruplar arası kapsam çakışması tespit edildi (tarih çakışıyor). Lütfen kategori/ürün kapsamlarını düzenleyin.', 'kns-dynamic-discounts-for-woocommerce' ) ] );
            }
        }

        // 3) BOGO cross-group conflict check
        // PERFORMANCE: Preload all group data if not already loaded
        if (!isset($all_groups)) {
            $all_groups = KNSDD_Data::get_groups();
            $preloaded_data = [ 'dates' => [], 'rules' => [] ];
            
            foreach ( $all_groups as $g ) {
                $preloaded_data['dates'][$g] = KNSDD_Data::get_dates_for_group( $g );
                $preloaded_data['rules'][$g] = KNSDD_Data::get_rules_for_group( $g );
            }
        }
        
        $bogo_result = KNSDD_Conflicts::check_bogo_cross_group_conflict( $group, $start_d, $end_d, $sanitized_rules, $preloaded_data );
        
        if ( isset($bogo_result['conflict']) && $bogo_result['conflict'] ) {
            $conflict_with = isset($bogo_result['with']) ? $bogo_result['with'] : '';
            $conflict_type = isset($bogo_result['type']) ? $bogo_result['type'] : '';
            
            $type_messages = [
                'trigger_product_overlap' => __( 'BOGO çakışması: "%s" grubuyla aynı tetikleyici ürün kullanılıyor. Aynı tarihlerde aynı tetikleyici ürün farklı gruplarda kullanılamaz.', 'kns-dynamic-discounts-for-woocommerce' ),
                'trigger_category_overlap' => __( 'BOGO çakışması: "%s" grubuyla aynı tetikleyici kategori kullanılıyor. Aynı tarihlerde aynı tetikleyici kategori farklı gruplarda kullanılamaz.', 'kns-dynamic-discounts-for-woocommerce' ),
                'trigger_category_product_overlap' => __( 'BOGO çakışması: "%s" grubuyla tetikleyici kapsam çakışması. Kategori ve ürün kombinasyonu çakışıyor.', 'kns-dynamic-discounts-for-woocommerce' ),
            ];
            
            $msg = isset($type_messages[$conflict_type]) 
                ? sprintf( $type_messages[$conflict_type], $conflict_with )
                : sprintf( __( 'BOGO çakışması: "%s" grubuyla çakışma tespit edildi. Lütfen tetikleyici ürün/kategori seçimini değiştirin.', 'kns-dynamic-discounts-for-woocommerce' ), $conflict_with );
            
            wp_send_json_error( [ 
                'code' => 'bogo_cross_group_conflict',
                'with' => $conflict_with,
                'type' => $conflict_type,
                'message' => $msg 
            ] );
        }

        // Çatışma yoksa kaydet
        KNSDD_Data::save_rules_for_group( $group, $sanitized_rules );
        $this->invalidate_cache();

        foreach ( $sanitized_rules as $key => $rule ) {
            if ( ! empty( $rule['allowed_customers'] ) && is_array( $rule['allowed_customers'] ) ) {
                $customer_details = [];
                foreach ( $rule['allowed_customers'] as $user_id ) {
                    $user = get_user_by( 'id', (int) $user_id );
                    if ( $user ) {
                        $customer_details[] = [
                            'id'   => $user->ID,
                            'name' => esc_html( $user->display_name . ' (' . $user->user_email . ')' ),
                        ];
                    }
                }
                $sanitized_rules[ $key ]['allowed_customer_details'] = $customer_details;
            }
            if ( ! empty( $rule['product'] ) && is_array( $rule['product'] ) ) {
                $product_details = [];
                foreach ( $rule['product'] as $product_id ) {
                    $product = wc_get_product( $product_id );
                    if ( $product ) {
                        $img_id = method_exists( $product, 'get_image_id' ) ? $product->get_image_id() : 0;
                        $thumb  = $img_id ? wp_get_attachment_image_url( $img_id, 'thumbnail' ) : '';
                        $status = method_exists( $product, 'get_stock_status' ) ? (string) $product->get_stock_status() : '';
                        $mstock = method_exists( $product, 'get_manage_stock' ) ? (bool) $product->get_manage_stock() : false;
                        $qty    = ( $mstock && method_exists( $product, 'get_stock_quantity' ) ) ? $product->get_stock_quantity() : null;
                        $product_details[] = [
                            'id'    => $product_id,
                            'name'  => $product->get_name(),
                            'thumb' => $thumb ? esc_url_raw( $thumb ) : '',
                            'sku'   => method_exists( $product, 'get_sku' ) ? (string) $product->get_sku() : '',
                            'stock_status' => $status,
                            'manage_stock' => $mstock ? 1 : 0,
                            'stock_qty'    => ( $qty === null ? null : intval( $qty ) ),
                        ];
                    }
                }
                $sanitized_rules[ $key ]['product_details'] = $product_details;
            }
        }

        wp_send_json_success( [ 'message' => __( 'Kurallar kaydedildi.', 'kns-dynamic-discounts-for-woocommerce' ), 'rules' => $sanitized_rules ] );
    }

    /**
     * Sanitize unknown extension payload recursively with depth/size constraints.
     * - Depth limit: 2
     * - Max items per level: 50
     * - Key whitelist: [A-Za-z0-9_-]{1,48}
     * - Strings trimmed and sanitized (max len 1024)
     */
    private function sanitize_ext_value( $value, $depth = 0 ) {
        if ( $depth > 2 ) { return []; }
        if ( is_array( $value ) ) {
            $out = [];
            $count = 0;
            foreach ( $value as $k => $v ) {
                if ( $count >= 50 ) { break; }
                $key_ok = is_int($k) || ( is_string($k) && preg_match('/^[A-Za-z0-9_\-]{1,48}$/', $k) );
                if ( ! $key_ok ) { continue; }
                $san = $this->sanitize_ext_value( $v, $depth + 1 );
                if ( $san === null ) { continue; }
                $out[$k] = $san;
                $count++;
            }
            return $out;
        }
        if ( is_string( $value ) ) {
            $s = sanitize_text_field( $value );
            if ( function_exists('mb_substr') ) { $s = mb_substr( $s, 0, 1024 ); } else { $s = substr( $s, 0, 1024 ); }
            return $s;
        }
        if ( is_bool( $value ) ) { return (bool) $value; }
        if ( is_int( $value ) ) { return intval( $value ); }
        if ( is_float( $value ) ) { return floatval( $value ); }
        if ( is_numeric( $value ) ) { return floatval( $value ); }
        return null;
    }

    /**
     * Batch olarak ürün->kategori (product_cat) eşleşmelerini döndürür.
     * Girdi: product_ids (int[])
     * Çıktı: { map: { [product_id]: [category_id, ...] } }
     */
    public function get_product_categories() {
        $this->check_permissions();
        $raw = isset( $_POST['product_ids'] ) ? $_POST['product_ids'] : [];
        if ( empty( $raw ) || ! is_array( $raw ) ) {
            wp_send_json_success( [ 'map' => [] ] );
        }
        $product_ids = array_values( array_filter( array_map( 'absint', $raw ), function( $v ) { return $v > 0; } ) );
        if ( empty( $product_ids ) ) {
            wp_send_json_success( [ 'map' => [] ] );
        }

        $terms = wp_get_object_terms( $product_ids, 'product_cat', [ 'fields' => 'all_with_object_id' ] );
        $map = [];
        if ( ! is_wp_error( $terms ) ) {
            foreach ( $terms as $t ) {
                $pid = isset( $t->object_id ) ? (int)$t->object_id : 0;
                $tid = isset( $t->term_id ) ? (int)$t->term_id : 0;
                if ( $pid && $tid ) {
                    if ( ! isset( $map[ $pid ] ) ) { $map[ $pid ] = []; }
                    $map[ $pid ][] = $tid;
                    
                    // Hiyerarşik kategori desteği: Üst kategorileri ekle (alt → üst)
                    $ancestors = get_ancestors( $tid, 'product_cat', 'taxonomy' );
                    if ( ! empty( $ancestors ) ) {
                        $map[ $pid ] = array_merge( $map[ $pid ], array_map( 'intval', $ancestors ) );
                    }
                }
            }
            
            // Duplikasyonları temizle
            foreach ( $map as $pid => $cats ) {
                $map[ $pid ] = array_values( array_unique( $cats ) );
            }
        }

        // Boş kalan ürünler için boş dizi koy (tutarlı çıktı)
        foreach ( $product_ids as $pid ) {
            if ( ! isset( $map[ $pid ] ) ) { $map[ $pid ] = []; }
        }

        wp_send_json_success( [ 'map' => $map ] );
    }
}
