<?php

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

class AIEO_DMM_Pricing_Engine {

    
    private static $instance = null;

    
    private $role_price_cache = array();

    
    private $brand_discount_snapshot = null;

    
    private $discount_schedule_snapshot = null;

    
    private $no_free_shipping_set = null;

    
    private $loyalty_cache = array();

    public static function instance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    
    
    

    
    public function get_effective_price($product_id, $user_id = null, $base = null) {
        $product_id = (int) $product_id;
        if ($product_id <= 0) {
            return $base !== null ? (float) $base : 0.0;
        }

        if ($user_id === null) {
            $user_id = get_current_user_id();
        }
        $user_id = (int) $user_id;

        $ctx_key = AIEO_DMM_Cache::user_context_key($user_id) . ':' . $product_id;
        $memo    = AIEO_DMM_Cache::get_l1('prices', $ctx_key);
        if ($memo !== null) {
            return (float) $memo;
        }

        if ($base === null) {
            $base = (float) $this->lookup_base_price($product_id);
        } else {
            $base = (float) $base;
        }

        $price = $this->resolve_price($product_id, $user_id, $base);
        AIEO_DMM_Cache::put_l1('prices', $ctx_key, $price);
        return $price;
    }

    
    public function apply_recipe_only($base_after_role, $product_id, $user_id = null) {
        $product_id = (int) $product_id;
        if ($product_id <= 0 || !is_numeric($base_after_role)) {
            return is_numeric($base_after_role) ? (float) $base_after_role : 0.0;
        }
        $base_after_role = (float) $base_after_role;
        if ($base_after_role <= 0) {
            return $base_after_role;
        }

        if ($user_id === null) {
            $user_id = get_current_user_id();
        }
        $user_id = (int) $user_id;

        
        
        
        $ctx_key = 'recipe_only:' . $product_id . ':'
                 . AIEO_DMM_Cache::user_context_key($user_id) . ':'
                 . number_format($base_after_role, 4, '.', '');
        $memo = AIEO_DMM_Cache::get_l1('prices', $ctx_key);
        if ($memo !== null) {
            return (float) $memo;
        }

        $settings = AIEO_DMM_Settings::get_all();
        $recipe   = isset($settings['recipe']) ? (array) $settings['recipe'] : array();
        $mode     = isset($settings['pricing_mode']) ? (string) $settings['pricing_mode'] : 'reactive';
        $force_reactive = !empty($GLOBALS['aieo_dmm_force_reactive']);

        if (!$force_reactive
            && $mode === 'precomputed'
            && class_exists('AIEO_DMM_Product_FP')
            && class_exists('AIEO_DMM_User_FP')) {
            $price = $this->recipe_only_precomputed($base_after_role, $product_id, $user_id, $recipe);
        } else {
            $price = $this->recipe_only_reactive($base_after_role, $product_id, $user_id, $recipe);
        }

        if ($price < 0) {
            $price = 0.0;
        }
        AIEO_DMM_Cache::put_l1('prices', $ctx_key, (float) $price);
        return (float) $price;
    }

    
    private function recipe_only_precomputed($base_after_role, $product_id, $user_id, array $recipe) {
        $fp  = AIEO_DMM_Product_FP::get($product_id);
        $ufp = AIEO_DMM_User_FP::get($user_id);
        if (!$fp) {
            return $this->recipe_only_reactive($base_after_role, $product_id, $user_id, $recipe);
        }

        $price         = (float) $base_after_role;
        $role_adjusted = $price;   

        $brand_pct = (float) ($fp['effective_combined_pct'] ?? 0);
        if ($brand_pct > 0 && !empty($recipe['brand_discount']) && $recipe['brand_discount'] !== 'off') {
            switch ($recipe['brand_discount']) {
                case 'stack_multiplicative': $price = $price * (1.0 - ($brand_pct / 100.0)); break;
                case 'stack_additive':       $price = $price - ($role_adjusted * ($brand_pct / 100.0)); break;
                case 'replace_role':
                    
                    $price = (float) ($fp['base_regular'] ?? $base_after_role) * (1.0 - ($brand_pct / 100.0));
                    break;
            }
        }

        $sched_pct = (float) ($fp['active_schedule_pct'] ?? 0);
        if ($sched_pct > 0) {
            $price = $price * (1.0 - ($sched_pct / 100.0));
        }

        if ($user_id > 0 && is_array($ufp) && !empty($recipe['loyalty_class']) && $recipe['loyalty_class'] !== 'off') {
            $loyal_pct = (float) ($ufp['loyalty_extra_pct_sum'] ?? 0)
                       + (float) ($ufp['personal_extra_pct'] ?? 0);
            $seq_expires = isset($ufp['sequence_expires_at']) ? (string) $ufp['sequence_expires_at'] : '';
            if (!empty($ufp['sequence_pct']) && $seq_expires !== '' && strtotime($seq_expires) > time()) {
                $loyal_pct += (float) $ufp['sequence_pct'];
            }
            if ($loyal_pct > 0) {
                switch ($recipe['loyalty_class']) {
                    case 'stack_multiplicative': $price = $price * (1.0 - ($loyal_pct / 100.0)); break;
                    case 'stack_additive':       $price = $price - ($role_adjusted * ($loyal_pct / 100.0)); break;
                    case 'replace_all':
                        $price = (float) ($fp['base_regular'] ?? $base_after_role) * (1.0 - ($loyal_pct / 100.0));
                        break;
                }
            }

            if (!empty($ufp['brand_overrides']) && is_array($ufp['brand_overrides'])) {
                $brand_ids = isset($fp['brand_term_ids']) ? (array) $fp['brand_term_ids'] : array();
                foreach ($brand_ids as $bid) {
                    if (isset($ufp['brand_overrides'][$bid])) {
                        $bo = (float) $ufp['brand_overrides'][$bid];
                        if ($bo > 0) {
                            $price = $price * (1.0 - ($bo / 100.0));
                        }
                    }
                }
            }
        }

        return $price;
    }

    
    private function recipe_only_reactive($base_after_role, $product_id, $user_id, array $recipe) {
        $price         = (float) $base_after_role;
        $role_adjusted = $price;

        $brand_pct = $this->lookup_brand_discount_percent($product_id);
        if ($brand_pct > 0 && !empty($recipe['brand_discount']) && $recipe['brand_discount'] !== 'off') {
            $base = (float) $this->lookup_base_price($product_id);
            switch ($recipe['brand_discount']) {
                case 'stack_multiplicative': $price = $price * (1.0 - ($brand_pct / 100.0)); break;
                case 'stack_additive':       $price = $price - ($role_adjusted * ($brand_pct / 100.0)); break;
                case 'replace_role':         $price = $base  * (1.0 - ($brand_pct / 100.0)); break;
            }
        }

        $sched_pct = $this->lookup_schedule_percent($product_id);
        if ($sched_pct > 0) {
            $price = $price * (1.0 - ($sched_pct / 100.0));
        }

        if ($user_id > 0 && !empty($recipe['loyalty_class']) && $recipe['loyalty_class'] !== 'off') {
            $loyal_pct = $this->get_loyalty_extra_percent($user_id);
            if ($loyal_pct > 0) {
                $base = (float) $this->lookup_base_price($product_id);
                switch ($recipe['loyalty_class']) {
                    case 'stack_multiplicative': $price = $price * (1.0 - ($loyal_pct / 100.0)); break;
                    case 'stack_additive':       $price = $price - ($role_adjusted * ($loyal_pct / 100.0)); break;
                    case 'replace_all':          $price = $base  * (1.0 - ($loyal_pct / 100.0)); break;
                }
            }
        }

        return $price;
    }

    
    public function is_excluded_from_free_shipping($product_id) {
        $product_id = (int) $product_id;
        if ($product_id <= 0) {
            return false;
        }

        
        
        
        $mode = class_exists('AIEO_DMM_Settings') ? AIEO_DMM_Settings::get('pricing_mode', 'reactive') : 'reactive';
        if ($mode === 'precomputed' && class_exists('AIEO_DMM_Product_FP')) {
            $fp = AIEO_DMM_Product_FP::get($product_id);
            if (is_array($fp) && array_key_exists('nfs_excluded', $fp)) {
                return !empty($fp['nfs_excluded']);
            }
        }

        
        $set = $this->get_no_free_shipping_set();

        if (isset($set[$product_id])) {
            return true;
        }

        
        $parent = (int) wp_get_post_parent_id($product_id);
        if ($parent > 0 && isset($set[$parent])) {
            return true;
        }

        return false;
    }

    
    public function get_loyalty_class_ids_for_user($user_id) {
        $user_id = (int) $user_id;
        if ($user_id <= 0) {
            return array();
        }

        if (!isset($this->loyalty_cache[$user_id])) {
            $this->loyalty_cache[$user_id] = $this->load_loyalty_for_user($user_id);
        }
        return array_keys($this->loyalty_cache[$user_id]);
    }

    
    public function get_loyalty_extra_percent($user_id) {
        $user_id = (int) $user_id;
        if ($user_id <= 0) {
            return 0.0;
        }
        if (!isset($this->loyalty_cache[$user_id])) {
            $this->loyalty_cache[$user_id] = $this->load_loyalty_for_user($user_id);
        }
        return (float) array_sum($this->loyalty_cache[$user_id]);
    }

    
    public function preload_role_prices(array $product_ids) {
        global $wpdb;

        $ids = array_filter(array_map('intval', $product_ids));
        $ids = array_unique($ids);
        $ids = array_diff($ids, array_keys($this->role_price_cache));
        if (empty($ids)) {
            return;
        }

        $table = $wpdb->prefix . 'aieo_dmm_role_prices';
        if (!$this->table_has_rows_column($table)) {
            return;
        }

        $placeholders = implode(',', array_fill(0, count($ids), '%d'));
        
        $sql = "SELECT * FROM {$table} WHERE product_id IN ({$placeholders}) OR parent_id IN ({$placeholders})";
        $args = array_merge(array_values($ids), array_values($ids));
        
        $rows = $wpdb->get_results($wpdb->prepare($sql, $args), ARRAY_A);

        if (!is_array($rows)) {
            return;
        }

        foreach ($rows as $row) {
            $pid = (int) $row['product_id'];
            $prices = array();
            foreach ($row as $col => $val) {
                if (strpos($col, 'role_') === 0 && $val !== null && $val !== '') {
                    $role = substr($col, 5); 
                    $prices[$role] = (float) $val;
                }
            }
            $this->role_price_cache[$pid] = $prices;
        }

        
        foreach ($ids as $id) {
            if (!isset($this->role_price_cache[$id])) {
                $this->role_price_cache[$id] = array();
            }
        }
    }

    
    
    

    
    private function resolve_price($product_id, $user_id, $base) {
        $settings = AIEO_DMM_Settings::get_all();
        $recipe   = $settings['recipe'];
        $mode     = isset($settings['pricing_mode']) ? (string) $settings['pricing_mode'] : 'reactive';

        $force_reactive = !empty($GLOBALS['aieo_dmm_force_reactive']);

        if (!$force_reactive
            && $mode === 'precomputed'
            && class_exists('AIEO_DMM_Product_FP')
            && class_exists('AIEO_DMM_User_FP')) {
            return $this->resolve_price_precomputed($product_id, $user_id, $base, $recipe);
        }

        
        $price = $base;
        if ($recipe['role_pricing'] === 'active') {
            $role_price = $this->lookup_role_price($product_id, $user_id, $settings['enabled_roles']);
            if ($role_price !== null && $role_price > 0) {
                $price = $role_price;
            }
        }
        $role_adjusted = $price;

        
        $brand_percent = $this->lookup_brand_discount_percent($product_id);
        if ($brand_percent > 0 && $recipe['brand_discount'] !== 'off') {
            switch ($recipe['brand_discount']) {
                case 'stack_multiplicative':
                    $price = $price * (1.0 - ($brand_percent / 100.0));
                    break;
                case 'stack_additive':
                    $price = $price - ($role_adjusted * ($brand_percent / 100.0));
                    break;
                case 'replace_role':
                    $price = $base * (1.0 - ($brand_percent / 100.0));
                    break;
            }
        }

        
        $sched_percent = $this->lookup_schedule_percent($product_id);
        if ($sched_percent > 0) {
            $price = $price * (1.0 - ($sched_percent / 100.0));
        }

        
        if ($user_id > 0 && $recipe['loyalty_class'] !== 'off') {
            $loyal_percent = $this->get_loyalty_extra_percent($user_id);
            if ($loyal_percent > 0) {
                switch ($recipe['loyalty_class']) {
                    case 'stack_multiplicative':
                        $price = $price * (1.0 - ($loyal_percent / 100.0));
                        break;
                    case 'stack_additive':
                        $price = $price - ($role_adjusted * ($loyal_percent / 100.0));
                        break;
                    case 'replace_all':
                        $price = $base * (1.0 - ($loyal_percent / 100.0));
                        break;
                }
            }
        }

        if ($price < 0) {
            $price = 0.0;
        }
        return (float) $price;
    }

    
    private function resolve_price_precomputed($product_id, $user_id, $base, $recipe) {
        $fp  = AIEO_DMM_Product_FP::get($product_id);
        $ufp = AIEO_DMM_User_FP::get($user_id);
        if (!$fp) {
            
            return $this->resolve_price_reactive_fallback($product_id, $user_id, $base, $recipe);
        }

        
        $price = $base;
        if ($recipe['role_pricing'] === 'active'
            && is_array($ufp) && !empty($ufp['enabled_role_column'])) {
            if (!isset($this->role_price_cache[$product_id])) {
                $this->preload_role_prices(array($product_id));
            }
            $map = isset($this->role_price_cache[$product_id]) ? $this->role_price_cache[$product_id] : array();
            $col = $ufp['enabled_role_column'];
            if (isset($map[$col]) && $map[$col] > 0) {
                $price = (float) $map[$col];
            }
        }
        $role_adjusted = $price;

        
        $brand_pct = (float) ($fp['effective_combined_pct'] ?? 0);
        if ($brand_pct > 0 && $recipe['brand_discount'] !== 'off') {
            switch ($recipe['brand_discount']) {
                case 'stack_multiplicative': $price = $price * (1.0 - ($brand_pct / 100.0)); break;
                case 'stack_additive':       $price = $price - ($role_adjusted * ($brand_pct / 100.0)); break;
                case 'replace_role':         $price = $base * (1.0 - ($brand_pct / 100.0)); break;
            }
        }

        
        $sched_pct = (float) ($fp['active_schedule_pct'] ?? 0);
        if ($sched_pct > 0) {
            $price = $price * (1.0 - ($sched_pct / 100.0));
        }

        
        if ($user_id > 0 && is_array($ufp) && $recipe['loyalty_class'] !== 'off') {
            $loyal_pct = (float) ($ufp['loyalty_extra_pct_sum'] ?? 0)
                       + (float) ($ufp['personal_extra_pct'] ?? 0);
            
            $seq_expires = isset($ufp['sequence_expires_at']) ? (string) $ufp['sequence_expires_at'] : '';
            if (!empty($ufp['sequence_pct']) && $seq_expires !== '' && strtotime($seq_expires) > time()) {
                $loyal_pct += (float) $ufp['sequence_pct'];
            }
            if ($loyal_pct > 0) {
                switch ($recipe['loyalty_class']) {
                    case 'stack_multiplicative': $price = $price * (1.0 - ($loyal_pct / 100.0)); break;
                    case 'stack_additive':       $price = $price - ($role_adjusted * ($loyal_pct / 100.0)); break;
                    case 'replace_all':          $price = $base  * (1.0 - ($loyal_pct / 100.0)); break;
                }
            }

            
            if (!empty($ufp['brand_overrides']) && is_array($ufp['brand_overrides'])) {
                $brand_ids = isset($fp['brand_term_ids']) ? (array) $fp['brand_term_ids'] : array();
                foreach ($brand_ids as $bid) {
                    if (isset($ufp['brand_overrides'][$bid])) {
                        $bo = (float) $ufp['brand_overrides'][$bid];
                        if ($bo > 0) {
                            $price = $price * (1.0 - ($bo / 100.0));
                        }
                    }
                }
            }
        }

        if ($price < 0) { $price = 0.0; }
        return (float) $price;
    }

    
    private function resolve_price_reactive_fallback($product_id, $user_id, $base, $recipe) {
        
        
        $old = $GLOBALS['aieo_dmm_force_reactive'] ?? false;
        $GLOBALS['aieo_dmm_force_reactive'] = true;
        try {
            return $this->resolve_price($product_id, $user_id, $base);
        } finally {
            $GLOBALS['aieo_dmm_force_reactive'] = $old;
        }
    }

    
    
    

    
    private function lookup_base_price($product_id) {
        if (!function_exists('wc_get_product')) {
            return 0.0;
        }
        $product = wc_get_product($product_id);
        if (!$product) {
            return 0.0;
        }
        $price = $product->get_regular_price('edit');
        return is_numeric($price) ? (float) $price : 0.0;
    }

    
    public function get_role_price($product_id, $user_id = null) {
        $product_id = (int) $product_id;
        if ($product_id <= 0) { return null; }
        if ($user_id === null) { $user_id = get_current_user_id(); }
        $enabled_roles = AIEO_DMM_Settings::get('enabled_roles', array());
        if (!is_array($enabled_roles) || empty($enabled_roles)) { return null; }
        return $this->lookup_role_price($product_id, (int) $user_id, $enabled_roles);
    }

    
    private function lookup_role_price($product_id, $user_id, array $enabled_roles) {
        if (empty($enabled_roles)) {
            return null;
        }

        if (!isset($this->role_price_cache[$product_id])) {
            $this->preload_role_prices(array($product_id));
        }

        $map = isset($this->role_price_cache[$product_id]) ? $this->role_price_cache[$product_id] : array();
        if (empty($map)) {
            return null;
        }

        $user_roles = array();
        if ($user_id > 0) {
            $user = get_userdata($user_id);
            if ($user && !empty($user->roles)) {
                $user_roles = array_values(array_intersect($user->roles, $enabled_roles));
            }
        }
        if (empty($user_roles)) {
            return null;
        }

        $best = null;
        foreach ($user_roles as $role) {
            $col = $this->sanitize_role_column_name($role);
            if (isset($map[$col]) && $map[$col] > 0) {
                if ($best === null || $map[$col] < $best) {
                    $best = (float) $map[$col];
                }
            }
        }
        return $best;
    }

    
    private function sanitize_role_column_name($role) {
        $clean = strtolower(preg_replace('/[^a-z0-9_]/', '_', $role));
        if ($clean === '') {
            return 'r';
        }
        if (preg_match('/^[0-9]/', $clean)) {
            $clean = 'r_' . $clean;
        }
        return $clean;
    }

    
    private function lookup_brand_discount_percent($product_id) {
        $rules = $this->get_brand_discount_rules();
        if (empty($rules)) {
            return 0.0;
        }

        $brand_terms = $this->get_product_brand_term_ids($product_id);
        $cat_terms   = $this->get_product_category_term_ids($product_id);
        $need_terms  = taxonomy_exists('pa_need')
            ? wp_get_object_terms($product_id, 'pa_need', array('fields' => 'ids'))
            : array();
        if (is_wp_error($need_terms)) { $need_terms = array(); }
        $need_terms = array_map('intval', (array) $need_terms);

        
        $self_ids = array((int) $product_id);
        $parent_pid = (int) wp_get_post_parent_id($product_id);
        if ($parent_pid > 0) { $self_ids[] = $parent_pid; }

        $max = 0.0;
        foreach ($rules as $rule) {
            
            
            
            
            
            if ((float) (isset($rule['min_subtotal']) ? $rule['min_subtotal'] : 0) > 0) { continue; }
            if (!empty($rule['product_ids'])) { continue; }

            
            
            if (!empty($rule['excluded_brand_ids'])    && !empty($brand_terms) && array_intersect($brand_terms, $rule['excluded_brand_ids']))    { continue; }
            if (!empty($rule['excluded_category_ids']) && !empty($cat_terms)   && array_intersect($cat_terms,   $rule['excluded_category_ids'])) { continue; }
            if (!empty($rule['excluded_need_ids'])     && !empty($need_terms)  && array_intersect($need_terms,  $rule['excluded_need_ids']))     { continue; }
            if (!empty($rule['excluded_product_ids'])  && array_intersect($self_ids, $rule['excluded_product_ids']))                              { continue; }

            $rule_need_ids   = isset($rule['need_term_ids']) && is_array($rule['need_term_ids']) ? $rule['need_term_ids'] : array();
            $has_brand_filter = !empty($rule['brand_term_id']);
            $has_cat_filter   = !empty($rule['category_term_ids']);
            $has_need_filter  = !empty($rule_need_ids);

            
            
            
            if ($rule['operator'] === 'OR') {
                $match = ($has_brand_filter && in_array((int) $rule['brand_term_id'], $brand_terms, true))
                      || ($has_cat_filter   && !empty($cat_terms)  && array_intersect($rule['category_term_ids'], $cat_terms))
                      || ($has_need_filter  && !empty($need_terms) && array_intersect($rule_need_ids,             $need_terms));
                if (!$has_brand_filter && !$has_cat_filter && !$has_need_filter) { $match = false; }
            } else {
                $brand_match = !$has_brand_filter
                    || (!empty($brand_terms) && in_array((int) $rule['brand_term_id'], $brand_terms, true));
                $cat_match   = !$has_cat_filter
                    || (!empty($cat_terms)   && array_intersect($rule['category_term_ids'], $cat_terms));
                $need_match  = !$has_need_filter
                    || (!empty($need_terms)  && array_intersect($rule_need_ids, $need_terms));
                $match = $brand_match && $cat_match && $need_match;
            }

            if ($match && $rule['percent'] > $max) {
                $max = (float) $rule['percent'];
            }
        }
        return $max;
    }

    
    private function get_product_brand_term_ids($product_id) {
        $taxonomy = class_exists('AIEO_DMM_Brand_Discounts') ? AIEO_DMM_Brand_Discounts::get_brand_taxonomy() : 'product_brand';
        if (!$taxonomy) { $taxonomy = 'product_brand'; }
        $terms = wp_get_object_terms($product_id, $taxonomy, array('fields' => 'ids'));
        if (is_wp_error($terms) || !is_array($terms) || empty($terms)) {
            $parent = (int) wp_get_post_parent_id($product_id);
            if ($parent > 0) {
                $terms = wp_get_object_terms($parent, $taxonomy, array('fields' => 'ids'));
            }
        }
        return is_array($terms) ? array_map('intval', $terms) : array();
    }

    
    private function get_product_category_term_ids($product_id) {
        $terms = wp_get_object_terms($product_id, 'product_cat', array('fields' => 'ids'));
        if (is_wp_error($terms) || !is_array($terms) || empty($terms)) {
            $parent = (int) wp_get_post_parent_id($product_id);
            if ($parent > 0) {
                $terms = wp_get_object_terms($parent, 'product_cat', array('fields' => 'ids'));
            }
        }
        return is_array($terms) ? array_map('intval', $terms) : array();
    }

    
    private function get_brand_discount_rules() {
        if ($this->brand_discount_snapshot !== null) {
            return $this->brand_discount_snapshot;
        }
        $this->brand_discount_snapshot = AIEO_DMM_Cache::remember(
            'brands',
            'active_rules',
            array($this, 'load_brand_discount_rules'),
            AIEO_DMM_Cache::TTL_SHORT
        );
        return $this->brand_discount_snapshot;
    }

    
    public function load_brand_discount_rules() {
        global $wpdb;

        $table = $wpdb->prefix . 'aieo_dmm_brand_discounts';
        if (!$this->table_exists($table)) {
            return array();
        }

        
        
        
        
        
        $cols = $wpdb->get_col("SHOW COLUMNS FROM {$table}");
        $cols = is_array($cols) ? $cols : array();
        $select_cols = array('brand_term_id', 'category_term_ids', 'rule_operator', 'percent');
        foreach (array('need_term_ids', 'min_subtotal', 'min_subtotal_scope', 'application_scope', 'excluded_brand_ids', 'excluded_category_ids', 'excluded_need_ids', 'product_ids', 'excluded_product_ids') as $opt_col) {
            if (in_array($opt_col, $cols, true)) { $select_cols[] = $opt_col; }
        }
        $select = 'SELECT ' . implode(', ', $select_cols);

        $today = current_time('Y-m-d');
        
        $rows = $wpdb->get_results($wpdb->prepare(
            "{$select} FROM {$table}
             WHERE enabled = 1
             AND (date_from IS NULL OR date_from <= %s)
             AND (date_to IS NULL OR date_to >= %s)",
            $today,
            $today
        ), ARRAY_A);

        $decode_ids = static function ($val) {
            if (empty($val)) { return array(); }
            $d = json_decode((string) $val, true);
            return is_array($d) ? array_values(array_filter(array_map('intval', $d))) : array();
        };

        $out = array();
        if (is_array($rows)) {
            foreach ($rows as $row) {
                $out[] = array(
                    'brand_term_id'         => (int) $row['brand_term_id'],
                    'category_term_ids'     => $decode_ids(isset($row['category_term_ids']) ? $row['category_term_ids'] : ''),
                    'need_term_ids'         => $decode_ids(isset($row['need_term_ids']) ? $row['need_term_ids'] : ''),
                    'operator'              => (isset($row['rule_operator']) && strtoupper($row['rule_operator']) === 'OR') ? 'OR' : 'AND',
                    'percent'               => (float) $row['percent'],
                    
                    
                    
                    
                    'min_subtotal'          => isset($row['min_subtotal']) ? (float) $row['min_subtotal'] : 0.0,
                    'min_subtotal_scope'    => isset($row['min_subtotal_scope']) ? (string) $row['min_subtotal_scope'] : 'targeted',
                    'application_scope'     => isset($row['application_scope']) ? (string) $row['application_scope'] : 'matching',
                    'excluded_brand_ids'    => $decode_ids(isset($row['excluded_brand_ids']) ? $row['excluded_brand_ids'] : ''),
                    'excluded_category_ids' => $decode_ids(isset($row['excluded_category_ids']) ? $row['excluded_category_ids'] : ''),
                    'excluded_need_ids'     => $decode_ids(isset($row['excluded_need_ids']) ? $row['excluded_need_ids'] : ''),
                    'product_ids'           => $decode_ids(isset($row['product_ids']) ? $row['product_ids'] : ''),
                    'excluded_product_ids'  => $decode_ids(isset($row['excluded_product_ids']) ? $row['excluded_product_ids'] : ''),
                );
            }
        }
        return $out;
    }

    
    public function load_brand_discount_snapshot() {
        $rules = $this->load_brand_discount_rules();
        $out   = array();
        foreach ($rules as $r) {
            $tid = (int) $r['brand_term_id'];
            if ($tid > 0 && $r['percent'] > 0 && (!isset($out[$tid]) || $r['percent'] > $out[$tid])) {
                $out[$tid] = (float) $r['percent'];
            }
        }
        return $out;
    }

    
    private function lookup_schedule_percent($product_id) {
        $snap = $this->get_discount_schedule_snapshot();
        return isset($snap[$product_id]) ? (float) $snap[$product_id] : 0.0;
    }

    
    private function get_discount_schedule_snapshot() {
        if ($this->discount_schedule_snapshot !== null) {
            return $this->discount_schedule_snapshot;
        }
        $this->discount_schedule_snapshot = AIEO_DMM_Cache::remember(
            'prices',
            'schedule_snapshot',
            array($this, 'load_discount_schedule_snapshot'),
            AIEO_DMM_Cache::TTL_SHORT
        );
        return $this->discount_schedule_snapshot;
    }

    
    public function load_discount_schedule_snapshot() {
        global $wpdb;

        $table = $wpdb->prefix . 'aieo_dmm_discount_schedules';
        if (!$this->table_exists($table)) {
            return array();
        }

        $today = current_time('Y-m-d');
        
        $rows = $wpdb->get_results($wpdb->prepare(
            "SELECT product_id, percent FROM {$table}
             WHERE active = 1
             AND (date_from IS NULL OR date_from <= %s)
             AND (date_to IS NULL OR date_to >= %s)",
            $today,
            $today
        ), ARRAY_A);

        $out = array();
        if (is_array($rows)) {
            foreach ($rows as $row) {
                $pid = (int) $row['product_id'];
                $pct = (float) $row['percent'];
                if ($pid > 0 && $pct > 0 && (!isset($out[$pid]) || $pct > $out[$pid])) {
                    $out[$pid] = $pct;
                }
            }
        }
        return $out;
    }

    
    private function get_no_free_shipping_set() {
        if ($this->no_free_shipping_set !== null) {
            return $this->no_free_shipping_set;
        }

        
        $cached = wp_cache_get('om_no_free_shipping_ids', 'om_shipping', false, $found);
        if ($found && is_array($cached)) {
            $this->no_free_shipping_set = $cached;
            return $cached;
        }

        $out = $this->load_no_free_shipping_set();
        wp_cache_set('om_no_free_shipping_ids', $out, 'om_shipping', AIEO_DMM_Cache::TTL_MEDIUM);
        $this->no_free_shipping_set = $out;
        return $out;
    }

    
    public function load_no_free_shipping_set() {
        global $wpdb;

        $out = array();
        $candidates = array(
            $wpdb->prefix . 'aieo_dmm_no_free_shipping',
            $wpdb->prefix . 'om_no_free_shipping',
        );
        foreach ($candidates as $table) {
            if (!$this->table_exists($table)) {
                continue;
            }
            
            $ids = $wpdb->get_col("SELECT product_id FROM {$table}");
            foreach ((array) $ids as $pid) {
                $out[(int) $pid] = true;
            }
        }
        return $out;
    }

    
    private function load_loyalty_for_user($user_id) {
        global $wpdb;

        $assign = $wpdb->prefix . 'aieo_dmm_loyalty_assignments';
        $defs   = $wpdb->prefix . 'aieo_dmm_loyalty_classes';

        if (!$this->table_exists($assign) || !$this->table_exists($defs)) {
            return array();
        }

        $key = 'user:' . $user_id;
        return AIEO_DMM_Cache::remember('loyalty', $key, function () use ($wpdb, $assign, $defs, $user_id) {
            
            $rows = $wpdb->get_results($wpdb->prepare(
                "SELECT d.id, d.extra_percent FROM {$assign} a
                 INNER JOIN {$defs} d ON d.id = a.class_id
                 WHERE a.user_id = %d AND d.enabled = 1",
                $user_id
            ), ARRAY_A);

            $out = array();
            if (is_array($rows)) {
                foreach ($rows as $row) {
                    $out[(int) $row['id']] = (float) $row['extra_percent'];
                }
            }
            return $out;
        }, AIEO_DMM_Cache::TTL_MEDIUM);
    }

    
    
    

    
    private function table_exists($table) {
        global $wpdb;

        static $cache = array();
        if (isset($cache[$table])) {
            return $cache[$table];
        }
        $found = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s",
            $table
        ));
        $cache[$table] = ((int) $found) > 0;
        return $cache[$table];
    }

    
    private function table_has_rows_column($table) {
        
        global $wpdb;
        static $cache = array();
        if (isset($cache[$table])) {
            return $cache[$table];
        }
        if (!$this->table_exists($table)) {
            return $cache[$table] = false;
        }
        $col = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = 'product_id'",
            $table
        ));
        return $cache[$table] = ((int) $col) > 0;
    }

    
    public function register_preload_hooks() {
        add_action('woocommerce_before_shop_loop', array($this, 'preload_archive'), 1);
        add_action('woocommerce_before_main_content', array($this, 'preload_archive'), 1);
    }

    
    public function preload_archive() {
        if (is_admin()) {
            return;
        }
        global $wp_query;
        if (!$wp_query || empty($wp_query->posts)) {
            return;
        }
        $ids = array();
        foreach ($wp_query->posts as $post) {
            if (isset($post->ID)) {
                $ids[] = (int) $post->ID;
            }
        }
        if (!empty($ids)) {
            $this->preload_role_prices($ids);
        }
    }
}
