<?php
/**
 * Dynamic Counts
 */

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

add_action('wp_ajax_askagento_get_counts', 'askagento_ajax_get_counts');
add_action('wp_ajax_nopriv_askagento_get_counts', 'askagento_ajax_get_counts');

/**
 * Sanitize filter `current` payload from POST (same shape as askagento_get_current_values() keys).
 *
 * @param mixed $current Raw $_POST['current'] or equivalent.
 * @return array<string, string|array<int, string>>
 */
function askagento_sanitize_filter_current_array($current) {
    if (!is_array($current)) {
        return [];
    }
    $sanitized = [];
    foreach ($current as $key => $val) {
        $key = sanitize_text_field((string) $key);
        if ($key === '') {
            continue;
        }
        if (!function_exists('askagento_sanitize_single_filter_value')) {
            if (is_array($val)) {
                $sanitized[$key] = array_map('sanitize_text_field', $val);
            } else {
                $sanitized[$key] = sanitize_text_field((string) $val);
            }
            continue;
        }
        if (is_array($val)) {
            $sanitized[$key] = array_values(
                array_filter(
                    array_map(
                        static function ($v) use ($key) {
                            return askagento_sanitize_single_filter_value($key, $v);
                        },
                        $val
                    )
                )
            );
        } else {
            $sanitized[$key] = askagento_sanitize_single_filter_value($key, $val);
        }
    }

    return $sanitized;
}

/**
 * AJAX handler for counts
 */
function askagento_ajax_get_counts() {
    check_ajax_referer('askagento_filter', 'nonce');

    $filter_id = isset($_POST['filter_id']) ? absint(wp_unslash($_POST['filter_id'])) : 0;
    $sanitized = askagento_sanitize_filter_current_array(isset($_POST['current']) ? (array) map_deep( wp_unslash( $_POST['current'] ), 'sanitize_text_field' ) : []);

    if (!$filter_id) {
        wp_send_json_error(['message' => 'Invalid filter']);
    }

    $counts = askagento_calculate_counts($filter_id, $sanitized);

    wp_send_json_success($counts);
}

/**
 * Calculate counts for all options given current selection
 */
function askagento_calculate_counts($filter_id, $current = []) {
    global $wpdb;
    
    $fields = askagento_get_fields($filter_id);
    $term_table = $wpdb->prefix . 'askagento_term_results';
    $cache_table = $wpdb->prefix . 'askagento_cache';
    
    // Get base product IDs (all products matching current filters)
    $base_ids = askagento_get_filtered_ids($filter_id, $current);
    
    $counts = [];
    
    foreach ($fields as $field) {
        $source = $field['source'];
        $parts = explode(':', $source, 2);
        if (count($parts) !== 2) continue;
        
        $source_type = $parts[0];
        $source_name = $parts[1];
        $field_name = askagento_get_field_name($source_type, $source_name);
        
        // Skip range fields - they don't have counts
        if ($field['type'] === 'range') {
            // But calculate min/max for current selection
            $counts[$field_name] = askagento_get_range_bounds($filter_id, $field_name, $base_ids);
            continue;
        }
        
        // Get counts for this field
        $counts[$field_name] = askagento_get_field_counts($filter_id, $field_name, $current, $base_ids);
    }
    
    return $counts;
}

/**
 * Get counts for single field
 */
function askagento_get_field_counts($filter_id, $field_name, $current, $base_ids) {
    global $wpdb;
    
    $term_table = $wpdb->prefix . 'askagento_term_results';
    
    // Get all options for this field
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query required to fetch all option values and result IDs from the plugin's custom term-results table for real-time count calculation.
    $options = $wpdb->get_results($wpdb->prepare(
        'SELECT field_value, result_ids FROM %i WHERE filter_id = %d AND field_name = %s',
        $term_table, $filter_id, $field_name
    ));
    
    $counts = [];
    
    // Current selection for THIS field (to exclude from intersection)
    $current_field_values = isset($current[$field_name]) ? (array) $current[$field_name] : [];
    
    // Build "other filters" base - what's selected in OTHER fields
    $other_current = $current;
    unset($other_current[$field_name]);
    
    // Get IDs matching all OTHER filters
    if (!empty($other_current)) {
        $other_ids = askagento_get_filtered_ids($filter_id, $other_current);
    } else {
        $other_ids = null; // null = all products
    }
    
    foreach ($options as $opt) {
        $value = $opt->field_value;
        $opt_ids = array_map('intval', explode(',', $opt->result_ids));
        $opt_ids = array_values(array_filter($opt_ids, static function ($id) {
            return $id > 0;
        }));
        if (function_exists('askagento_normalize_to_catalog_product_ids')) {
            $opt_ids = askagento_normalize_to_catalog_product_ids($opt_ids);
        }
        
        // Intersect with other filters
        if ($other_ids !== null) {
            $available = array_intersect($opt_ids, $other_ids);
        } else {
            $available = $opt_ids;
        }
        
        $counts[$value] = count($available);
    }
    
    return $counts;
}

/**
 * Get min/max for range field based on current selection
 */
function askagento_get_range_bounds($filter_id, $field_name, $base_ids) {
    global $wpdb;
    
    $cache_table = $wpdb->prefix . 'askagento_cache';
    
    if ($base_ids === null) {
        // No filter - get global min/max
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query required to compute global min/max bounds from the plugin's custom cache table for real-time range field calculation.
        $result = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT MIN(field_value_num) as min_val, MAX(field_value_num) as max_val
                 FROM %i
                 WHERE filter_id = %d AND field_name = %s AND field_value_num IS NOT NULL',
                $cache_table,
                $filter_id,
                $field_name
            )
        );

        if (
            ! askagento_cache_has_numeric_rows_for_field($filter_id, $field_name)
            && $field_name === ASKAGENTO_NUM_PREFIX . 'price'
            && function_exists('askagento_get_wc_options')
        ) {
            $settings = askagento_get_settings($filter_id);
            $post_types = ! empty($settings['post_types']) ? $settings['post_types'] : ['product'];
            $wb = askagento_get_wc_options('price', $post_types);

            return [
                'min'   => (float) ($wb['min'] ?? 0),
                'max'   => (float) ($wb['max'] ?? 0),
                'count' => null,
            ];
        }
    } elseif (empty($base_ids)) {
        return ['min' => 0, 'max' => 0, 'count' => 0];
    } else {
        $ids_list = implode(',', array_map('intval', $base_ids));
        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Direct query required to compute filtered min/max bounds from the plugin's custom cache table. $ids_list is cast through intval(), so it can only hold integers; it is interpolated rather than bound because the list can hold thousands of runtime-generated post IDs.
        $result = $wpdb->get_row($wpdb->prepare(
            "SELECT MIN(field_value_num) as min_val, MAX(field_value_num) as max_val
             FROM %i
             WHERE filter_id = %d AND field_name = %s AND field_value_num IS NOT NULL AND post_id IN ({$ids_list})",
            $cache_table, $filter_id, $field_name
        ));
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    }
    
    return [
        'min'   => (float) ($result ? ($result->min_val ?? 0) : 0),
        'max'   => (float) ($result ? ($result->max_val ?? 0) : 0),
        'count' => $base_ids === null ? null : count($base_ids),
    ];
}

/**
 * Get total count for current selection
 */
function askagento_get_total_count($filter_id, $current = []) {
    $ids = askagento_get_filtered_ids($filter_id, $current);
    
    if ($ids === null) {
        // Count all products
        global $wpdb;
        $settings = askagento_get_settings($filter_id);
        $post_types = $settings['post_types'];
        $placeholders = implode(', ', array_fill(0, count($post_types), '%s'));

        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Direct query required to count all published products across configured post types; real-time result must not be cached. $placeholders holds only generated %s tokens (array_fill over count()); the post types themselves are bound by prepare().
        return (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type IN ({$placeholders}) AND post_status = 'publish'",
                $post_types
            )
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
    }
    
    return count($ids);
}
