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

/**
 * Shared validator for cross-group conflict checks
 */
class KNSDD_Conflicts {

    private static function parse_range($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; // 0 = infinity
        return [ 'min' => $mn, 'max' => $mx ];
    }

    private static function ranges_overlap_nums($A, $B) {
        return ($A['min'] <= $B['max']) && ($B['min'] <= $A['max']);
    }

    public static function dates_overlap($aS, $aE, $bS, $bE) {
        if (!$aS || !$aE || !$bS || !$bE) return false; // empty dates disable cross-group validation
        $A1 = self::parse_ymd($aS); $A2 = self::parse_ymd($aE); $B1 = self::parse_ymd($bS); $B2 = self::parse_ymd($bE);
        if (!$A1 || !$A2 || !$B1 || !$B2) return false;
        return ($A1 <= $B2) && ($B1 <= $A2);
    }

    private static function parse_ymd($s) {
        if (empty($s)) return null;
        $p = explode('-', (string)$s);
        if (count($p)!==3) return null;
        return mktime(0,0,0, (int)$p[1], (int)$p[2], (int)$p[0]);
    }

    private static function is_effective_rule($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));
    }

    /**
     * İki kural arasında müşteri bazlı çakışma var mı kontrol eder
     * Uses subset logic: specific customers ⊂ all members ⊂ everyone
     * 
     * @param bool $login_required1 İlk kuralın login_required değeri
     * @param array $allowed_customers1 İlk kuralın allowed_customers değeri
     * @param bool $first_order_only1 İlk kuralın first_order_only değeri
     * @param bool $login_required2 İkinci kuralın login_required değeri
     * @param array $allowed_customers2 İkinci kuralın allowed_customers değeri
     * @param bool $first_order_only2 İkinci kuralın first_order_only değeri
     * @return bool True = çakışma var (customer sets intersect), False = çakışma yok
     */
    private static function has_customer_conflict($login_required1, $allowed_customers1, $first_order_only1, $login_required2, $allowed_customers2, $first_order_only2) {
        // first_order_only creates disjoint sets (mutually exclusive)
        // first_order ∩ non_first_order = ∅
        if (($first_order_only1 && !$first_order_only2) || (!$first_order_only1 && $first_order_only2)) {
            return false; // No intersection
        }

        // Both have specific customers
        if (!empty($allowed_customers1) && !empty($allowed_customers2)) {
            $common = array_intersect($allowed_customers1, $allowed_customers2);
            if (empty($common)) {
                return false; // No common customers = no intersection
            }
            // Has common customers = intersection exists = conflict
            return true;
        }

        // One has specific customers, other doesn't
        // Specific customers ⊂ All members ⊂ Everyone
        // If one is specific and other is broader, intersection exists = conflict
        if (!empty($allowed_customers1) || !empty($allowed_customers2)) {
            // Specific customers are always a subset of members/everyone
            // Therefore intersection exists = conflict
            return true;
        }

        // Neither has specific customers
        // Check login_required (members vs everyone)
        if ($login_required1 !== $login_required2) {
            // One is "members only", other is "everyone"
            // Members ⊂ Everyone, intersection exists = conflict
            return true;
        }

        // Both have same login requirement (both members or both everyone)
        // Intersection exists = conflict
        return true;
    }

    /**
     * Build effective sets from given rules
     * Returns [category_only, product_ids, category_only_ranges]
     */
    public static function build_sets_from_rules($rules) {
        $category_only = [];
        $product_ids = [];
        $category_only_ranges = [];
        foreach ((array)$rules as $r) {
            if (! self::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)) {
                $category_only[] = $cat;
                $rng = self::parse_range( isset($r['min']) ? $r['min'] : 0, isset($r['max']) ? $r['max'] : 0 );
                if (!isset($category_only_ranges[$cat])) { $category_only_ranges[$cat] = []; }
                $category_only_ranges[$cat][] = $rng;
            }
            if (!empty($products)) { $product_ids = array_merge($product_ids, $products); }
        }
        $category_only = array_values(array_unique($category_only));
        $product_ids   = array_values(array_unique($product_ids));
        return [ $category_only, $product_ids, $category_only_ranges ];
    }

    /**
     * Check if setting dates for a group would conflict with other groups.
     * @param string $group Current group name
     * @param string $start Start date (Y-m-d)
     * @param string $end End date (Y-m-d)
     * @param array $my_sets [category_only, product_ids, category_only_ranges] from build_sets_from_rules
     * @param array $preloaded_data Required: ['dates' => [], 'rules' => []] for performance
     * @return array [ 'conflict' => bool, 'with' => string, 'type' => string (optional) ]
     */
    public static function check_cross_group_conflict($group, $start, $end, $my_sets, $preloaded_data) {
        list($my_category_only, $my_product_ids, $my_category_only_ranges) = $my_sets;

        if (empty($my_category_only) && empty($my_product_ids)) {
            return [ 'conflict' => false, 'with' => '' ];
        }

        $all_groups = array_keys($preloaded_data['dates']);
        $all_dates = $preloaded_data['dates'];
        $all_rules = $preloaded_data['rules'];
        $my_rules = isset($all_rules[$group]) ? $all_rules[$group] : [];
        
        if (empty($my_rules)) {
            return [ 'conflict' => false, 'with' => '' ];
        }

        // Grup tipi bazlı filtreleme: Sadece aynı tipteki gruplarla çakışma kontrolü
        $my_rule_type = KNSDD_Data::get_group_rule_type($group);

        foreach ($all_groups as $g) {
            if ($g === $group) continue;
            
            // Farklı tipteki grupları atla (sabit indirim vs özel kampanya)
            $other_rule_type = KNSDD_Data::get_group_rule_type($g);
            if ($my_rule_type !== $other_rule_type) {
                continue;
            }
            
            $od = isset($all_dates[$g]) ? $all_dates[$g] : [];
            $other_rules = isset($all_rules[$g]) ? $all_rules[$g] : [];
            
            $oS = isset($od['start']) ? $od['start'] : '';
            $oE = isset($od['end']) ? $od['end'] : '';
            
            // Tamamlanmış kampanyaları atla (performans optimizasyonu)
            // Yeni kampanya bugün veya gelecek tarihli olduğundan çakışma imkansız
            // NOT: Bugün biten kampanyalar kontrol edilmeli (saat belirsizliği önlenir)
            if (!empty($oE) && strtotime($oE) < strtotime(current_time('Y-m-d'))) {
                continue;
            }
            
            if (! self::dates_overlap($start, $end, $oS, $oE)) {
                continue;
            }

            if (empty($other_rules)) continue;
            $other_sets = self::build_sets_from_rules($other_rules);
            $other_category_only = isset($other_sets[0]) ? (array)$other_sets[0] : [];
            $other_product_ids   = isset($other_sets[1]) ? (array)$other_sets[1] : [];
            $other_category_only_ranges = isset($other_sets[2]) ? (array)$other_sets[2] : [];

            if (empty($other_category_only) && empty($other_product_ids)) continue;

            if (!empty($other_product_ids)) {
                foreach ($other_product_ids as $pid) {
                    if (!isset($product_to_cats[(int)$pid])) {
                        $terms = get_the_terms((int)$pid, 'product_cat');
                        if (is_array($terms)) {
                            $product_to_cats[(int)$pid] = wp_list_pluck($terms, 'term_id');
                        }
                    }
                }
            }

            $my_cat_set    = array_flip($my_category_only);
            $other_cat_set = array_flip($other_category_only);

            // a) my products vs other's category-only WITH range check
            foreach ($my_product_ids as $pid) {
                $cats = isset($product_to_cats[$pid]) ? (array)$product_to_cats[$pid] : [];
                foreach ($cats as $c) { 
                    if (isset($other_cat_set[(int)$c])) {
                        // Aralık ve müşteri bazlı çakışma kontrolü
                        $has_conflict = false;
                        foreach ($my_rules as $my_rule) {
                            if (isset($my_rule['product']) && is_array($my_rule['product']) && in_array($pid, $my_rule['product'])) {
                                $my_range = self::parse_range(
                                    isset($my_rule['min']) ? $my_rule['min'] : 0,
                                    isset($my_rule['max']) ? $my_rule['max'] : 0
                                );
                                
                                foreach ($other_rules as $other_rule) {
                                    if (isset($other_rule['category']) && $other_rule['category'] == $c) {
                                        $other_range = self::parse_range(
                                            isset($other_rule['min']) ? $other_rule['min'] : 0,
                                            isset($other_rule['max']) ? $other_rule['max'] : 0
                                        );
                                        
                                        // Aralıklar kesişiyorsa ve müşteri çakışması varsa
                                        if (self::ranges_overlap_nums($my_range, $other_range)) {
                                            if (self::has_customer_conflict(
                                                isset($my_rule['login_required']) ? $my_rule['login_required'] : false,
                                                isset($my_rule['allowed_customers']) ? $my_rule['allowed_customers'] : [],
                                                isset($my_rule['first_order_only']) ? $my_rule['first_order_only'] : false,
                                                isset($other_rule['login_required']) ? $other_rule['login_required'] : false,
                                                isset($other_rule['allowed_customers']) ? $other_rule['allowed_customers'] : [],
                                                isset($other_rule['first_order_only']) ? $other_rule['first_order_only'] : false
                                            )) {
                                                $has_conflict = true;
                                                break 2;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if ($has_conflict) {
                            return [ 'conflict' => true, 'with' => $g ];
                        }
                    }
                }
            }
            // b) my category-only vs other's products WITH range check
            foreach ($other_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])) {
                        // Aralık ve müşteri bazlı çakışma kontrolü
                        $has_conflict = false;
                        foreach ($my_rules as $my_rule) {
                            if (isset($my_rule['category']) && $my_rule['category'] == $c) {
                                $my_range = self::parse_range(
                                    isset($my_rule['min']) ? $my_rule['min'] : 0,
                                    isset($my_rule['max']) ? $my_rule['max'] : 0
                                );
                                
                                foreach ($other_rules as $other_rule) {
                                    if (isset($other_rule['product']) && is_array($other_rule['product']) && in_array($pid, $other_rule['product'])) {
                                        $other_range = self::parse_range(
                                            isset($other_rule['min']) ? $other_rule['min'] : 0,
                                            isset($other_rule['max']) ? $other_rule['max'] : 0
                                        );
                                        
                                        // Aralıklar kesişiyorsa ve müşteri çakışması varsa
                                        if (self::ranges_overlap_nums($my_range, $other_range)) {
                                            if (self::has_customer_conflict(
                                                isset($my_rule['login_required']) ? $my_rule['login_required'] : false,
                                                isset($my_rule['allowed_customers']) ? $my_rule['allowed_customers'] : [],
                                                isset($my_rule['first_order_only']) ? $my_rule['first_order_only'] : false,
                                                isset($other_rule['login_required']) ? $other_rule['login_required'] : false,
                                                isset($other_rule['allowed_customers']) ? $other_rule['allowed_customers'] : [],
                                                isset($other_rule['first_order_only']) ? $other_rule['first_order_only'] : false
                                            )) {
                                                $has_conflict = true;
                                                break 2;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if ($has_conflict) {
                            return [ 'conflict' => true, 'with' => $g ];
                        }
                    }
                }
            }
            
            // c) product-product: direct overlap with range check
            if (!empty($my_product_ids) && !empty($other_product_ids)) {
                $common_products = array_intersect($my_product_ids, $other_product_ids);
                if (!empty($common_products)) {
                    foreach ($common_products as $pid) {
                        // Bu ürün için her iki gruptaki kuralları bul ve aralık kontrolü yap
                        $has_conflict = false;
                        foreach ($my_rules as $my_rule) {
                            if (isset($my_rule['product']) && is_array($my_rule['product']) && in_array($pid, $my_rule['product'])) {
                                $my_range = self::parse_range(
                                    isset($my_rule['min']) ? $my_rule['min'] : 0,
                                    isset($my_rule['max']) ? $my_rule['max'] : 0
                                );
                                
                                foreach ($other_rules as $other_rule) {
                                    if (isset($other_rule['product']) && is_array($other_rule['product']) && in_array($pid, $other_rule['product'])) {
                                        $other_range = self::parse_range(
                                            isset($other_rule['min']) ? $other_rule['min'] : 0,
                                            isset($other_rule['max']) ? $other_rule['max'] : 0
                                        );
                                        
                                        // Aralıklar kesişiyorsa ve müşteri çakışması varsa
                                        if (self::ranges_overlap_nums($my_range, $other_range)) {
                                            if (self::has_customer_conflict(
                                                isset($my_rule['login_required']) ? $my_rule['login_required'] : false,
                                                isset($my_rule['allowed_customers']) ? $my_rule['allowed_customers'] : [],
                                                isset($my_rule['first_order_only']) ? $my_rule['first_order_only'] : false,
                                                isset($other_rule['login_required']) ? $other_rule['login_required'] : false,
                                                isset($other_rule['allowed_customers']) ? $other_rule['allowed_customers'] : [],
                                                isset($other_rule['first_order_only']) ? $other_rule['first_order_only'] : false
                                            )) {
                                                $has_conflict = true;
                                                break 2;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if ($has_conflict) {
                            return [ 'conflict' => true, 'with' => $g ];
                        }
                    }
                }
            }
            
            // d) category-category range overlap on same category
            if (!empty($my_category_only_ranges) && !empty($other_category_only_ranges)) {
                foreach ($my_category_only_ranges as $catId => $rangesA) {
                    if (!isset($other_category_only_ranges[(int)$catId])) { continue; }
                    $rangesB = $other_category_only_ranges[(int)$catId];
                    foreach ($rangesA as $ra) {
                        foreach ($rangesB as $rb) {
                            if (self::ranges_overlap_nums($ra, $rb)) {
                                // Müşteri bazlı çakışma kontrolü
                                $has_conflict = false;
                                foreach ($my_rules as $my_rule) {
                                    if (isset($my_rule['category']) && $my_rule['category'] == $catId) {
                                        foreach ($other_rules as $other_rule) {
                                            if (isset($other_rule['category']) && $other_rule['category'] == $catId) {
                                                if (self::has_customer_conflict(
                                                    isset($my_rule['login_required']) ? $my_rule['login_required'] : false,
                                                    isset($my_rule['allowed_customers']) ? $my_rule['allowed_customers'] : [],
                                                    isset($my_rule['first_order_only']) ? $my_rule['first_order_only'] : false,
                                                    isset($other_rule['login_required']) ? $other_rule['login_required'] : false,
                                                    isset($other_rule['allowed_customers']) ? $other_rule['allowed_customers'] : [],
                                                    isset($other_rule['first_order_only']) ? $other_rule['first_order_only'] : false
                                                )) {
                                                    $has_conflict = true;
                                                    break 2;
                                                }
                                            }
                                        }
                                    }
                                }
                                if ($has_conflict) {
                                    return [ 'conflict' => true, 'with' => $g ];
                                }
                            }
                        }
                    }
                }
            }
        }
        return [ 'conflict' => false, 'with' => '' ];
    }

    /**
     * Build product_id => [category_id, ...] map in batch for performance.
     *
     * @param int[] $product_ids
     * @return array
     */
    private static function build_product_to_cats($product_ids) {
        $product_ids = array_values(array_filter(array_map('absint', (array)$product_ids)));
        $map = [];
        if (empty($product_ids)) { return $map; }
        $terms = wp_get_object_terms( $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($map[$pid])) $map[$pid] = [];
                    $map[$pid][] = $tid;
                }
            }
        }
        // Ensure all products exist in map, even if they have no categories
        foreach ($product_ids as $pid) { if (!isset($map[$pid])) { $map[$pid] = []; } }
        return $map;
    }

    /**
     * Dataset-aware variant of cross-group conflict check.
     * It mirrors check_cross_group_conflict but operates on an explicit dataset instead of KNSDD_Data.
     *
     * @param string $group         Current group name
     * @param string $start         Current group start date (Y-m-d)
     * @param string $end           Current group end date (Y-m-d)
     * @param array  $my_sets       [category_only, product_ids, category_only_ranges] from build_sets_from_rules
     * @param array  $my_rules      Current group's rules (sanitized)
     * @param array  $groups_dataset Assoc array: [ groupName => [ 'dates' => ['start'=>..,'end'=>..], 'rules' => [] ] ]
     * @return array [ 'conflict' => bool, 'with' => string ]
     */
    public static function check_cross_group_conflict_dataset($group, $start, $end, $my_sets, $my_rules, $groups_dataset) {
        $groups_dataset = is_array($groups_dataset) ? $groups_dataset : [];
        list($my_category_only, $my_product_ids, $my_category_only_ranges) = $my_sets;

        if (empty($my_category_only) && empty($my_product_ids)) {
            return [ 'conflict' => false, 'with' => '' ];
        }

        if (empty($my_rules) || !is_array($my_rules)) {
            return [ 'conflict' => false, 'with' => '' ];
        }

        // Grup tipi bazlı filtreleme: Sadece aynı tipteki gruplarla çakışma kontrolü
        $my_rule_type = KNSDD_Data::get_group_rule_type($group);

        // Collect all product IDs from dataset for batch category lookup
        $all_product_ids = array_merge($my_product_ids);
        foreach ($groups_dataset as $g_name => $g_data) {
            if ($g_name === $group) continue;
            
            // Farklı tipteki grupları atla (sabit indirim vs özel kampanya)
            $other_rule_type = KNSDD_Data::get_group_rule_type($g_name);
            if ($my_rule_type !== $other_rule_type) {
                continue;
            }
            
            // Tamamlanmış kampanyaları atla (performans optimizasyonu)
            $other_dates = isset($g_data['dates']) ? (array)$g_data['dates'] : [];
            $oE = isset($other_dates['end']) ? $other_dates['end'] : '';
            // NOT: Bugün biten kampanyalar kontrol edilmeli (saat belirsizliği önlenir)
            if (!empty($oE) && strtotime($oE) < strtotime(current_time('Y-m-d'))) {
                continue;
            }
            
            $other_rules = isset($g_data['rules']) ? (array)$g_data['rules'] : [];
            $other_sets = self::build_sets_from_rules($other_rules);
            $other_product_ids = isset($other_sets[1]) ? (array)$other_sets[1] : [];
            $all_product_ids = array_merge($all_product_ids, $other_product_ids);
        }
        $all_product_ids = array_values(array_unique(array_filter(array_map('absint', $all_product_ids))));
        
        // Build product->category mapping once for all products
        $product_to_cats = self::build_product_to_cats($all_product_ids);

        foreach ($groups_dataset as $g_name => $g_data) {
            if ($g_name === $group) continue;
            
            // Farklı tipteki grupları atla (sabit indirim vs özel kampanya)
            $other_rule_type = KNSDD_Data::get_group_rule_type($g_name);
            if ($my_rule_type !== $other_rule_type) {
                continue;
            }
            
            $other_dates = isset($g_data['dates']) ? (array)$g_data['dates'] : [];
            $oS = isset($other_dates['start']) ? $other_dates['start'] : '';
            $oE = isset($other_dates['end']) ? $other_dates['end'] : '';
            
            // Tamamlanmış kampanyaları atla (performans optimizasyonu)
            // Yeni kampanya bugün veya gelecek tarihli olduğundan çakışma imkansız
            // NOT: Bugün biten kampanyalar kontrol edilmeli (saat belirsizliği önlenir)
            if (!empty($oE) && strtotime($oE) < strtotime(current_time('Y-m-d'))) {
                continue;
            }
            
            if (! self::dates_overlap($start, $end, $oS, $oE)) {
                continue;
            }

            $other_rules = isset($g_data['rules']) ? (array)$g_data['rules'] : [];
            if (empty($other_rules)) continue;
            
            $other_sets = self::build_sets_from_rules($other_rules);
            $other_category_only = isset($other_sets[0]) ? (array)$other_sets[0] : [];
            $other_product_ids   = isset($other_sets[1]) ? (array)$other_sets[1] : [];
            $other_category_only_ranges = isset($other_sets[2]) ? (array)$other_sets[2] : [];

            if (empty($other_category_only) && empty($other_product_ids)) continue;

            $my_cat_set    = array_flip($my_category_only);
            $other_cat_set = array_flip($other_category_only);

            // a) my products vs other's category-only WITH range check
            foreach ($my_product_ids as $pid) {
                $cats = isset($product_to_cats[$pid]) ? (array)$product_to_cats[$pid] : [];
                foreach ($cats as $c) { 
                    if (isset($other_cat_set[(int)$c])) {
                        // Aralık ve müşteri bazlı çakışma kontrolü
                        $has_conflict = false;
                        foreach ($my_rules as $my_rule) {
                            if (isset($my_rule['product']) && is_array($my_rule['product']) && in_array($pid, $my_rule['product'])) {
                                $my_range = self::parse_range(
                                    isset($my_rule['min']) ? $my_rule['min'] : 0,
                                    isset($my_rule['max']) ? $my_rule['max'] : 0
                                );
                                
                                foreach ($other_rules as $other_rule) {
                                    if (isset($other_rule['category']) && $other_rule['category'] == $c) {
                                        $other_range = self::parse_range(
                                            isset($other_rule['min']) ? $other_rule['min'] : 0,
                                            isset($other_rule['max']) ? $other_rule['max'] : 0
                                        );
                                        
                                        // Aralıklar kesişiyorsa ve müşteri çakışması varsa
                                        if (self::ranges_overlap_nums($my_range, $other_range)) {
                                            if (self::has_customer_conflict(
                                                isset($my_rule['login_required']) ? $my_rule['login_required'] : false,
                                                isset($my_rule['allowed_customers']) ? $my_rule['allowed_customers'] : [],
                                                isset($my_rule['first_order_only']) ? $my_rule['first_order_only'] : false,
                                                isset($other_rule['login_required']) ? $other_rule['login_required'] : false,
                                                isset($other_rule['allowed_customers']) ? $other_rule['allowed_customers'] : [],
                                                isset($other_rule['first_order_only']) ? $other_rule['first_order_only'] : false
                                            )) {
                                                $has_conflict = true;
                                                break 2;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if ($has_conflict) {
                            return [ 'conflict' => true, 'with' => $g_name ];
                        }
                    }
                }
            }
            
            // b) my category-only vs other's products WITH range check
            foreach ($other_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])) {
                        // Aralık ve müşteri bazlı çakışma kontrolü
                        $has_conflict = false;
                        foreach ($my_rules as $my_rule) {
                            if (isset($my_rule['category']) && $my_rule['category'] == $c) {
                                $my_range = self::parse_range(
                                    isset($my_rule['min']) ? $my_rule['min'] : 0,
                                    isset($my_rule['max']) ? $my_rule['max'] : 0
                                );
                                
                                foreach ($other_rules as $other_rule) {
                                    if (isset($other_rule['product']) && is_array($other_rule['product']) && in_array($pid, $other_rule['product'])) {
                                        $other_range = self::parse_range(
                                            isset($other_rule['min']) ? $other_rule['min'] : 0,
                                            isset($other_rule['max']) ? $other_rule['max'] : 0
                                        );
                                        
                                        // Aralıklar kesişiyorsa ve müşteri çakışması varsa
                                        if (self::ranges_overlap_nums($my_range, $other_range)) {
                                            if (self::has_customer_conflict(
                                                isset($my_rule['login_required']) ? $my_rule['login_required'] : false,
                                                isset($my_rule['allowed_customers']) ? $my_rule['allowed_customers'] : [],
                                                isset($my_rule['first_order_only']) ? $my_rule['first_order_only'] : false,
                                                isset($other_rule['login_required']) ? $other_rule['login_required'] : false,
                                                isset($other_rule['allowed_customers']) ? $other_rule['allowed_customers'] : [],
                                                isset($other_rule['first_order_only']) ? $other_rule['first_order_only'] : false
                                            )) {
                                                $has_conflict = true;
                                                break 2;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if ($has_conflict) {
                            return [ 'conflict' => true, 'with' => $g_name ];
                        }
                    }
                }
            }
            
            // c) product-product: direct overlap with range check
            if (!empty($my_product_ids) && !empty($other_product_ids)) {
                $common_products = array_intersect($my_product_ids, $other_product_ids);
                if (!empty($common_products)) {
                    foreach ($common_products as $pid) {
                        // Bu ürün için her iki gruptaki kuralları bul ve aralık kontrolü yap
                        $has_conflict = false;
                        foreach ($my_rules as $my_rule) {
                            if (isset($my_rule['product']) && is_array($my_rule['product']) && in_array($pid, $my_rule['product'])) {
                                $my_range = self::parse_range(
                                    isset($my_rule['min']) ? $my_rule['min'] : 0,
                                    isset($my_rule['max']) ? $my_rule['max'] : 0
                                );
                                
                                foreach ($other_rules as $other_rule) {
                                    if (isset($other_rule['product']) && is_array($other_rule['product']) && in_array($pid, $other_rule['product'])) {
                                        $other_range = self::parse_range(
                                            isset($other_rule['min']) ? $other_rule['min'] : 0,
                                            isset($other_rule['max']) ? $other_rule['max'] : 0
                                        );
                                        
                                        // Aralıklar kesişiyorsa ve müşteri çakışması varsa
                                        if (self::ranges_overlap_nums($my_range, $other_range)) {
                                            if (self::has_customer_conflict(
                                                isset($my_rule['login_required']) ? $my_rule['login_required'] : false,
                                                isset($my_rule['allowed_customers']) ? $my_rule['allowed_customers'] : [],
                                                isset($my_rule['first_order_only']) ? $my_rule['first_order_only'] : false,
                                                isset($other_rule['login_required']) ? $other_rule['login_required'] : false,
                                                isset($other_rule['allowed_customers']) ? $other_rule['allowed_customers'] : [],
                                                isset($other_rule['first_order_only']) ? $other_rule['first_order_only'] : false
                                            )) {
                                                $has_conflict = true;
                                                break 2;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if ($has_conflict) {
                            return [ 'conflict' => true, 'with' => $g_name ];
                        }
                    }
                }
            }
            
            // d) category-category range overlap on same category
            if (!empty($my_category_only_ranges) && !empty($other_category_only_ranges)) {
                foreach ($my_category_only_ranges as $catId => $rangesA) {
                    if (!isset($other_category_only_ranges[(int)$catId])) { continue; }
                    $rangesB = $other_category_only_ranges[(int)$catId];
                    foreach ($rangesA as $ra) {
                        foreach ($rangesB as $rb) {
                            if (self::ranges_overlap_nums($ra, $rb)) {
                                // Customer-based conflict check
                                $has_conflict = false;
                                foreach ($my_rules as $my_rule) {
                                    if (isset($my_rule['category']) && $my_rule['category'] == $catId) {
                                        foreach ($other_rules as $other_rule) {
                                            if (isset($other_rule['category']) && $other_rule['category'] == $catId) {
                                                if (self::has_customer_conflict(
                                                    isset($my_rule['login_required']) ? $my_rule['login_required'] : false,
                                                    isset($my_rule['allowed_customers']) ? $my_rule['allowed_customers'] : [],
                                                    isset($my_rule['first_order_only']) ? $my_rule['first_order_only'] : false,
                                                    isset($other_rule['login_required']) ? $other_rule['login_required'] : false,
                                                    isset($other_rule['allowed_customers']) ? $other_rule['allowed_customers'] : [],
                                                    isset($other_rule['first_order_only']) ? $other_rule['first_order_only'] : false
                                                )) {
                                                    $has_conflict = true;
                                                    break 2;
                                                }
                                            }
                                        }
                                    }
                                }
                                if ($has_conflict) {
                                    return [ 'conflict' => true, 'with' => $g_name ];
                                }
                            }
                        }
                    }
                }
            }
            
            // d) Campaign type conflict: same product with different campaign types
            $my_campaign_map = []; // pid => campaign_type
            foreach ($my_rules as $r) {
                if (isset($r['rule_type']) && $r['rule_type'] === 'special_campaign') {
                    $campaign_type = isset($r['campaign_type']) ? $r['campaign_type'] : '';
                    // cart_total_discount is scope-free, skip
                    if ($campaign_type === 'cart_total_discount') continue;
                    
                    $products = [];
                    
                    // Standard campaigns: use 'product' field
                    if (isset($r['product']) && is_array($r['product'])) {
                        $products = array_merge($products, $r['product']);
                    }
                    
                    // buy_x_get_y_discount: collect both trigger and target products
                    if ($campaign_type === 'buy_x_get_y_discount') {
                        if (isset($r['trigger_products']) && is_array($r['trigger_products'])) {
                            $products = array_merge($products, $r['trigger_products']);
                        }
                        if (isset($r['target_products']) && is_array($r['target_products'])) {
                            $products = array_merge($products, $r['target_products']);
                        }
                    }
                    
                    foreach ($products as $pid) {
                        $pid = absint($pid);
                        if ($pid > 0) {
                            $my_campaign_map[$pid] = $campaign_type;
                        }
                    }
                }
            }
            
            if (!empty($my_campaign_map)) {
                foreach ($other_rules as $r) {
                    if (isset($r['rule_type']) && $r['rule_type'] === 'special_campaign') {
                        $other_campaign_type = isset($r['campaign_type']) ? $r['campaign_type'] : '';
                        if ($other_campaign_type === 'cart_total_discount') continue;
                        
                        $other_products = [];
                        
                        // Standard campaigns
                        if (isset($r['product']) && is_array($r['product'])) {
                            $other_products = array_merge($other_products, $r['product']);
                        }
                        
                        // buy_x_get_y_discount
                        if ($other_campaign_type === 'buy_x_get_y_discount') {
                            if (isset($r['trigger_products']) && is_array($r['trigger_products'])) {
                                $other_products = array_merge($other_products, $r['trigger_products']);
                            }
                            if (isset($r['target_products']) && is_array($r['target_products'])) {
                                $other_products = array_merge($other_products, $r['target_products']);
                            }
                        }
                        
                        foreach ($other_products as $pid) {
                            $pid = absint($pid);
                            if (isset($my_campaign_map[$pid]) && $my_campaign_map[$pid] !== $other_campaign_type) {
                                return [ 'conflict' => true, 'with' => $g_name, 'type' => 'campaign_type_mismatch' ];
                            }
                        }
                    }
                }
            }
        }
        return [ 'conflict' => false, 'with' => '' ];
    }

    /**
     * Check BOGO cross-group conflicts
     * 
     * @param string $group Current group name
     * @param string $start Start date (Y-m-d)
     * @param string $end End date (Y-m-d)
     * @param array $my_rules Current group's rules
     * @param array $preloaded_data Required: ['dates' => [], 'rules' => []] for performance
     * @return array [ 'conflict' => bool, 'with' => string, 'type' => string, 'details' => array ]
     */
    public static function check_bogo_cross_group_conflict($group, $start, $end, $my_rules, $preloaded_data) {
        // Filter BOGO campaigns only
        $my_bogo_rules = array_filter((array)$my_rules, function($r) {
            return isset($r['rule_type']) 
                && $r['rule_type'] === 'special_campaign'
                && isset($r['campaign_type'])
                && $r['campaign_type'] === 'buy_x_get_y_discount';
        });
        
        if (empty($my_bogo_rules)) {
            return ['conflict' => false];
        }
        
        // Collect trigger products and categories
        $my_trigger_data = self::extract_bogo_scope($my_bogo_rules, 'trigger');
        
        if (empty($my_trigger_data['products']) && empty($my_trigger_data['categories'])) {
            return ['conflict' => false];
        }
        
        $all_groups = array_keys($preloaded_data['dates']);
        $all_dates = $preloaded_data['dates'];
        $all_rules = $preloaded_data['rules'];
        
        foreach ($all_groups as $other_group) {
            if ($other_group === $group) continue;
            
            $other_dates = isset($all_dates[$other_group]) ? $all_dates[$other_group] : [];
            $other_rules = isset($all_rules[$other_group]) ? $all_rules[$other_group] : [];
            
            $oS = isset($other_dates['start']) ? $other_dates['start'] : '';
            $oE = isset($other_dates['end']) ? $other_dates['end'] : '';
            
            if (!self::dates_overlap($start, $end, $oS, $oE)) {
                continue;
            }
            
            // Get other group's BOGO rules
            $other_bogo_rules = array_filter((array)$other_rules, function($r) {
                return isset($r['rule_type']) 
                    && $r['rule_type'] === 'special_campaign'
                    && isset($r['campaign_type'])
                    && $r['campaign_type'] === 'buy_x_get_y_discount';
            });
            
            if (empty($other_bogo_rules)) continue;
            
            $other_trigger_data = self::extract_bogo_scope($other_bogo_rules, 'trigger');
            
            // Detect trigger conflict
            $conflict_result = self::detect_bogo_conflict(
                $my_trigger_data,
                $other_trigger_data,
                $my_bogo_rules,
                $other_bogo_rules
            );
            
            if ($conflict_result['conflict']) {
                return [
                    'conflict' => true,
                    'with' => $other_group,
                    'type' => $conflict_result['type'],
                    'details' => $conflict_result['details']
                ];
            }
            
            // Target conflict is NOT checked because:
            // - Different triggers mean different conditions
            // - Each campaign applies to its own target_quantity
            // - Multiple BOGO campaigns can target the same product if enough quantity exists
            // - Frontend logic handles which discount applies to which item
        }
        
        return ['conflict' => false];
    }

    /**
     * Dataset-aware variant for import operations
     */
    public static function check_bogo_cross_group_conflict_dataset($group, $start, $end, $my_rules, $groups_dataset) {
        $groups_dataset = is_array($groups_dataset) ? $groups_dataset : [];
        
        $my_bogo_rules = array_filter((array)$my_rules, function($r) {
            return isset($r['rule_type']) 
                && $r['rule_type'] === 'special_campaign'
                && isset($r['campaign_type'])
                && $r['campaign_type'] === 'buy_x_get_y_discount';
        });
        
        if (empty($my_bogo_rules)) {
            return ['conflict' => false];
        }
        
        $my_trigger_data = self::extract_bogo_scope($my_bogo_rules, 'trigger');
        
        if (empty($my_trigger_data['products']) && empty($my_trigger_data['categories'])) {
            return ['conflict' => false];
        }
        
        foreach ($groups_dataset as $other_group => $other_data) {
            if ($other_group === $group) continue;
            
            $other_dates = isset($other_data['dates']) ? (array)$other_data['dates'] : [];
            $oS = isset($other_dates['start']) ? $other_dates['start'] : '';
            $oE = isset($other_dates['end']) ? $other_dates['end'] : '';
            
            if (!self::dates_overlap($start, $end, $oS, $oE)) {
                continue;
            }
            
            $other_rules = isset($other_data['rules']) ? (array)$other_data['rules'] : [];
            $other_bogo_rules = array_filter($other_rules, function($r) {
                return isset($r['rule_type']) 
                    && $r['rule_type'] === 'special_campaign'
                    && isset($r['campaign_type'])
                    && $r['campaign_type'] === 'buy_x_get_y_discount';
            });
            
            if (empty($other_bogo_rules)) continue;
            
            $other_trigger_data = self::extract_bogo_scope($other_bogo_rules, 'trigger');
            
            $conflict_result = self::detect_bogo_conflict(
                $my_trigger_data,
                $other_trigger_data,
                $my_bogo_rules,
                $other_bogo_rules
            );
            
            if ($conflict_result['conflict']) {
                return [
                    'conflict' => true,
                    'with' => $other_group,
                    'type' => $conflict_result['type'],
                    'details' => $conflict_result['details']
                ];
            }
            
            // Target conflict is NOT checked (same reason as above)
        }
        
        return ['conflict' => false];
    }

    /**
     * Extract trigger or target scope from BOGO rules
     */
    private static function extract_bogo_scope($bogo_rules, $prefix) {
        $products = [];
        $categories = [];
        
        foreach ($bogo_rules as $rule) {
            $prod_key = $prefix . '_products';
            $cat_key = $prefix . '_category';
            
            if (!empty($rule[$prod_key]) && is_array($rule[$prod_key])) {
                $products = array_merge($products, array_map('absint', $rule[$prod_key]));
            }
            
            if (!empty($rule[$cat_key])) {
                $categories[] = absint($rule[$cat_key]);
            }
        }
        
        return [
            'products' => array_values(array_unique(array_filter($products))),
            'categories' => array_values(array_unique(array_filter($categories)))
        ];
    }

    /**
     * Detect specific type of BOGO conflict
     */
    private static function detect_bogo_conflict($my_trigger, $other_trigger, $my_rules, $other_rules) {
        // A) Direct product overlap
        if (!empty($my_trigger['products']) && !empty($other_trigger['products'])) {
            $overlap = array_intersect($my_trigger['products'], $other_trigger['products']);
            if (!empty($overlap)) {
                // Check customer segmentation - if different segments, no conflict
                if (self::has_different_customer_segments($my_rules, $other_rules)) {
                    return ['conflict' => false];
                }
                
                return [
                    'conflict' => true,
                    'type' => 'trigger_product_overlap',
                    'details' => ['products' => array_values($overlap)]
                ];
            }
        }
        
        // B) Direct category overlap
        if (!empty($my_trigger['categories']) && !empty($other_trigger['categories'])) {
            $overlap = array_intersect($my_trigger['categories'], $other_trigger['categories']);
            if (!empty($overlap)) {
                // Check customer segmentation
                if (self::has_different_customer_segments($my_rules, $other_rules)) {
                    return ['conflict' => false];
                }
                
                return [
                    'conflict' => true,
                    'type' => 'trigger_category_overlap',
                    'details' => ['categories' => array_values($overlap)]
                ];
            }
        }
        
        // C) Category + Product AND logic conflict
        // My category contains other's specific products OR vice versa
        if (!empty($my_trigger['categories']) && !empty($other_trigger['products'])) {
            $conflict_products = self::check_category_product_overlap(
                $my_trigger['categories'],
                $other_trigger['products']
            );
            if (!empty($conflict_products)) {
                // Check customer segmentation
                if (self::has_different_customer_segments($my_rules, $other_rules)) {
                    return ['conflict' => false];
                }
                
                return [
                    'conflict' => true,
                    'type' => 'trigger_category_product_overlap',
                    'details' => ['products' => $conflict_products, 'my_categories' => $my_trigger['categories']]
                ];
            }
        }
        
        if (!empty($other_trigger['categories']) && !empty($my_trigger['products'])) {
            $conflict_products = self::check_category_product_overlap(
                $other_trigger['categories'],
                $my_trigger['products']
            );
            if (!empty($conflict_products)) {
                // Check customer segmentation
                if (self::has_different_customer_segments($my_rules, $other_rules)) {
                    return ['conflict' => false];
                }
                
                return [
                    'conflict' => true,
                    'type' => 'trigger_category_product_overlap',
                    'details' => ['products' => $conflict_products, 'other_categories' => $other_trigger['categories']]
                ];
            }
        }
        
        return ['conflict' => false];
    }

    /**
     * Check if two rule sets have different customer segments
     * Returns true if segments are mutually exclusive (no conflict)
     */
    private static function has_different_customer_segments($my_rules, $other_rules) {
        // Collect customer segments from all rules
        $my_segments = self::extract_customer_segments($my_rules);
        $other_segments = self::extract_customer_segments($other_rules);
        
        $my_login = $my_segments['login_required'];
        $other_login = $other_segments['login_required'];
        $my_first_order = $my_segments['first_order_only'];
        $other_first_order = $other_segments['first_order_only'];
        $my_customers = $my_segments['customers'];
        $other_customers = $other_segments['customers'];
        
        // Case 1: Both have specific customer lists
        if (!empty($my_customers) && !empty($other_customers)) {
            $overlap = array_intersect($my_customers, $other_customers);
            if (empty($overlap)) {
                return true; // No common customers, no conflict
            }
            // Has overlap = intersection exists = conflict
            return false;
        }
        
        // Case 2: First order segmentation check
        if ($my_first_order !== $other_first_order) {
            // One is first order only, other is all orders
            // First orders are subset of all orders, conflict exists
            return false;
        }
        
        // Case 3: One has specific customers, other doesn't
        // Specific customers can be members or guests, so we can't assume they're different
        if (!empty($my_customers) || !empty($other_customers)) {
            // One is specific, other is "all" (members or everyone)
            // Specific customers are subset of "all", conflict exists
            return false;
        }
        
        // Case 4: Neither has specific customers, check login_required
        // Both are "all members" or "all customers"
        if ($my_login !== $other_login) {
            // One requires login (members only), other doesn't (everyone)
            // Members are subset of everyone, conflict exists
            return false;
        }
        
        // Same segments (both members or both everyone)
        return false;
    }

    /**
     * Extract customer segmentation from rules
     */
    private static function extract_customer_segments($rules) {
        $login_required = false;
        $first_order_only = false;
        $customers = [];
        
        foreach ((array)$rules as $rule) {
            // Login required check
            if (!empty($rule['login_required'])) {
                $login_required = true;
            }
            
            // First order only check
            if (!empty($rule['first_order_only'])) {
                $first_order_only = true;
            }
            
            // Specific customers
            if (!empty($rule['allowed_customers']) && is_array($rule['allowed_customers'])) {
                $customers = array_merge($customers, array_map('absint', $rule['allowed_customers']));
            }
        }
        
        return [
            'login_required' => $login_required,
            'first_order_only' => $first_order_only,
            'customers' => array_values(array_unique(array_filter($customers)))
        ];
    }

    /**
     * Check if products belong to categories
     */
    private static function check_category_product_overlap($categories, $products) {
        $conflict_products = [];
        $product_to_cats = self::build_product_to_cats($products);
        
        foreach ($products as $pid) {
            $prod_cats = isset($product_to_cats[$pid]) ? $product_to_cats[$pid] : [];
            foreach ($categories as $cat_id) {
                if (in_array($cat_id, $prod_cats, true)) {
                    $conflict_products[] = $pid;
                    break;
                }
            }
        }
        
        return array_values(array_unique($conflict_products));
    }
}
