<?php
/**
 * Query Filtering
 */

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

add_action('pre_get_posts', 'askagento_filter_query');
add_filter('posts_clauses', 'askagento_filter_clauses', 10, 2);

/**
 * Modify main query based on filter params
 */
function askagento_filter_query($query) {
    // Only frontend main query
    if (is_admin() || !$query->is_main_query()) {
        return;
    }
    
    // Check if we have filter params
    $params = askagento_get_current_values();
    if (empty($params)) {
        return;
    }
    
    // Process taxonomy filters
    $tax_query = $query->get('tax_query') ?: [];
    
    foreach ($params as $key => $value) {
        if (strpos($key, ASKAGENTO_TAX_PREFIX) !== 0) {
            continue;
        }
        
        $taxonomy = substr($key, strlen(ASKAGENTO_TAX_PREFIX));
        
        if (!taxonomy_exists($taxonomy)) {
            continue;
        }
        
        $terms = is_array($value) ? $value : [$value];
        $terms = array_filter($terms);
        
        if (empty($terms)) {
            continue;
        }
        
        $tax_query[] = [
            'taxonomy' => $taxonomy,
            'field'    => 'slug',
            'terms'    => $terms,
            'operator' => 'IN',
        ];
    }
    
    if (!empty($tax_query)) {
        $tax_query['relation'] = 'AND';
        $query->set('tax_query', $tax_query);
    }
    
    // Process meta filters
    $meta_query = $query->get('meta_query') ?: [];
    
    foreach ($params as $key => $value) {
        // String meta
        if (strpos($key, ASKAGENTO_META_PREFIX) === 0) {
            $meta_key = substr($key, strlen(ASKAGENTO_META_PREFIX));
            $values = is_array($value) ? $value : [$value];
            $values = array_filter($values);
            
            if (!empty($values)) {
                $meta_query[] = [
                    'key'     => $meta_key,
                    'value'   => $values,
                    'compare' => 'IN',
                ];
            }
        }
        
        // Numeric meta (range)
        if (strpos($key, ASKAGENTO_NUM_PREFIX) === 0) {
            $meta_key = substr($key, strlen(ASKAGENTO_NUM_PREFIX));

            // Resolve wc source name to actual meta key (e.g. price → _price)
            if (function_exists('askagento_wc_source_to_meta_key')) {
                $resolved = askagento_wc_source_to_meta_key($meta_key);
                if ($resolved) {
                    $meta_key = $resolved;
                }
            }
            
            // Parse range value (min-max)
            if (strpos($value, '-') !== false) {
                $parts = explode('-', $value);
                $min = isset($parts[0]) && $parts[0] !== '' ? (float) $parts[0] : null;
                $max = isset($parts[1]) && $parts[1] !== '' ? (float) $parts[1] : null;
                
                if ($min !== null) {
                    $meta_query[] = [
                        'key'     => $meta_key,
                        'value'   => $min,
                        'compare' => '>=',
                        'type'    => 'NUMERIC',
                    ];
                }
                
                if ($max !== null) {
                    $meta_query[] = [
                        'key'     => $meta_key,
                        'value'   => $max,
                        'compare' => '<=',
                        'type'    => 'NUMERIC',
                    ];
                }
            }
        }
    }
    
    if (!empty($meta_query)) {
        $meta_query['relation'] = 'AND';
        $query->set('meta_query', $meta_query);
    }
    
    // Mark query as filtered
    $query->set('askagento_filtered', true);
}

/**
 * Advanced filtering via SQL clauses (for cache-based filtering)
 */
function askagento_filter_clauses($clauses, $query)
{
    // Only continue when the AskAgento cache filter is active.
    if (!$query->get('askagento_use_cache')) {
        return $clauses;
    }

    global $wpdb;

    $filter_id = absint($query->get('askagento_filter_id'));
    $params    = askagento_get_current_values();

    if (empty($params) || !$filter_id) {
        return $clauses;
    }

    $cache_table = $wpdb->prefix . 'askagento_cache';
    $subqueries  = array();

    foreach ($params as $key => $value) {
        $field_name   = '';
        $field_values = array();
        $is_range     = false;

        if (0 === strpos($key, ASKAGENTO_TAX_PREFIX)) {
            $field_name   = $key;
            $field_values = is_array($value) ? $value : array($value);
        } elseif (0 === strpos($key, ASKAGENTO_META_PREFIX)) {
            $field_name   = $key;
            $field_values = is_array($value) ? $value : array($value);
        } elseif (0 === strpos($key, ASKAGENTO_NUM_PREFIX)) {
            $field_name = $key;
            $is_range   = true;
        }

        if (!$field_name) {
            continue;
        }

        if ($is_range && is_string($value) && false !== strpos($value, '-')) {
            list($range_min, $range_max) = askagento_parse_range_filter_bounds($value);

            if (null === $range_min && null === $range_max) {
                continue;
            }

            list($global_min, $global_max) = askagento_cache_numeric_field_bounds(
                $filter_id,
                $field_name
            );

            if (null !== $global_min && null !== $global_max) {
                $covers_min = null === $range_min || $range_min <= $global_min + 0.000001;
                $covers_max = null === $range_max || $range_max >= $global_max - 0.000001;

                if ($covers_min && $covers_max) {
                    continue;
                }
            }

            if (null !== $range_min && null !== $range_max) {
                $subqueries[] = $wpdb->prepare(
                    'SELECT DISTINCT post_id
                    FROM %i
                    WHERE filter_id = %d
                    AND field_name = %s
                    AND field_value_num >= %f
                    AND field_value_num <= %f',
                    $cache_table,
                    $filter_id,
                    $field_name,
                    $range_min,
                    $range_max
                );
            } elseif (null !== $range_min) {
                $subqueries[] = $wpdb->prepare(
                    'SELECT DISTINCT post_id
                    FROM %i
                    WHERE filter_id = %d
                    AND field_name = %s
                    AND field_value_num >= %f',
                    $cache_table,
                    $filter_id,
                    $field_name,
                    $range_min
                );
            } elseif (null !== $range_max) {
                $subqueries[] = $wpdb->prepare(
                    'SELECT DISTINCT post_id
                    FROM %i
                    WHERE filter_id = %d
                    AND field_name = %s
                    AND field_value_num <= %f',
                    $cache_table,
                    $filter_id,
                    $field_name,
                    $range_max
                );
            }
        } elseif (!empty($field_values)) {
            $field_values = array_map('strval', $field_values);

            $placeholders = implode(
                ', ',
                array_fill(0, count($field_values), '%s')
            );

            $query_values = array_merge(
                array(
                    $cache_table,
                    $filter_id,
                    $field_name,
                ),
                $field_values
            );

            // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $placeholders holds only generated %s tokens (array_fill over count()); every value, including the table name via %i, is bound through $query_values. The replacement count is passed as a single array, which prepare() accepts but the sniff cannot count statically.
            $subqueries[] = $wpdb->prepare(
                "SELECT DISTINCT post_id
                FROM %i
                WHERE filter_id = %d
                AND field_name = %s
                AND field_value IN ({$placeholders})",
                $query_values
            );
            // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
        }
    }

    if (empty($subqueries)) {
        return $clauses;
    }

    // Intersect the results of all active filter subqueries.
    $final_sql = $subqueries[0];

    for ($index = 1; $index < count($subqueries); $index++) {
        $alias = 'askagento_sq_' . absint($index);

        $final_sql = sprintf(
            'SELECT post_id FROM (%1$s) AS %2$s WHERE post_id IN (%3$s)',
            $final_sql,
            $alias,
            $subqueries[$index]
        );
    }

    /*
     * Each subquery has already been prepared above. The posts table name is
     * generated by WordPress and does not contain user-provided input.
     */
    // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    $clauses['where'] .= " AND {$wpdb->posts}.ID IN ({$final_sql})";

    return $clauses;
}

/**
 * Min/max numeric values indexed for this filter field (price, etc.).
 *
 * @return array{0: float|null, 1: float|null}
 */
function askagento_cache_numeric_field_bounds($filter_id, $field_name) {
    global $wpdb;

    $filter_id = (int) $filter_id;
    if ($filter_id <= 0 || $field_name === '') {
        return [null, null];
    }

    $cache_table = $wpdb->prefix . 'askagento_cache';
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query required to retrieve numeric min/max bounds from the plugin's custom cache table for range filter optimisation.
    $row = $wpdb->get_row(
        $wpdb->prepare(
            'SELECT MIN(field_value_num) AS nmin, MAX(field_value_num) AS nmax
             FROM %i
             WHERE filter_id = %d AND field_name = %s AND field_value_num IS NOT NULL',
            $cache_table,
            $filter_id,
            $field_name
        )
    );

    if (!$row || $row->nmin === null || $row->nmax === null) {
        return [null, null];
    }

    return [(float) $row->nmin, (float) $row->nmax];
}

/**
 * Parse "min-max" from range hidden input (non-negative storefront ranges).
 *
 * @return array{0: float|null, 1: float|null}
 */
function askagento_parse_range_filter_bounds($value) {
    $raw = trim((string) $value);
    if ($raw === '' || strpos($raw, '-') === false) {
        return [null, null];
    }

    $dash_at = strpos($raw, '-');
    $left = trim(substr($raw, 0, $dash_at));
    $right = trim(substr($raw, $dash_at + 1));

    $rmin = ($left !== '' && is_numeric($left)) ? (float) $left : null;
    $rmax = ($right !== '' && is_numeric($right)) ? (float) $right : null;

    return [$rmin, $rmax];
}

/**
 * Map requested taxonomy facet values to real term slugs (GET/AJAX may send labels or encoded slugs).
 *
 * @param string               $field_name e.g. _askagento_t_pa_color
 * @param array<int, string>   $values
 * @return array<int, string>
 */
function askagento_resolve_taxonomy_slugs_for_term_lookup($field_name, array $values) {
    if (strpos($field_name, ASKAGENTO_TAX_PREFIX) !== 0) {
        return array_values(array_filter(array_map('trim', $values)));
    }

    $taxonomy = substr($field_name, strlen(ASKAGENTO_TAX_PREFIX));
    if (!taxonomy_exists($taxonomy)) {
        return array_values(array_filter(array_map('trim', $values)));
    }

    $out = [];
    foreach ($values as $v) {
        $v = trim((string) $v);
        if ($v === '') {
            continue;
        }

        $term = get_term_by('slug', $v, $taxonomy);
        if (!$term || is_wp_error($term)) {
            $term = get_term_by('name', wp_unslash($v), $taxonomy);
        }
        if ((!$term || is_wp_error($term)) && function_exists('sanitize_title')) {
            $term = get_term_by('slug', sanitize_title($v), $taxonomy);
        }

        if ($term && !is_wp_error($term)) {
            $out[] = $term->slug;
        } else {
            $out[] = $v;
        }
    }

    return array_values(array_unique($out));
}

/**
 * Get filtered product IDs using term_results table (fastest)
 */
function askagento_get_filtered_ids($filter_id, $params = null)
{
    global $wpdb;

    $filter_id = absint($filter_id);

    if (null === $params) {
        $params = askagento_get_current_values();
    }

    if (empty($params)) {
        return null; // No filter means all products.
    }

    $term_table  = $wpdb->prefix . 'askagento_term_results';
    $cache_table = $wpdb->prefix . 'askagento_cache';
    $result_sets = array();

    foreach ($params as $key => $value) {
        if (
            0 === strpos($key, ASKAGENTO_TAX_PREFIX)
            || 0 === strpos($key, ASKAGENTO_META_PREFIX)
        ) {
            $field_name   = $key;
            $field_values = is_array($value) ? $value : array($value);

            $field_values = array_values(
                array_filter(
                    array_map('trim', $field_values)
                )
            );

            if (empty($field_values)) {
                continue;
            }

            $field_values = askagento_resolve_taxonomy_slugs_for_term_lookup(
                $field_name,
                $field_values
            );

            if (empty($field_values)) {
                continue;
            }

            $placeholders = implode(
                ', ',
                array_fill(0, count($field_values), '%s')
            );

            $query_values = array_merge(
                array(
                    $term_table,
                    $filter_id,
                    $field_name,
                ),
                $field_values
            );

            // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Direct query required to retrieve result ID sets from the plugin's custom term-results table for taxonomy/meta facet intersection. $placeholders holds only generated %s tokens (array_fill over count()); every value, including the table name via %i, is bound through $query_values, passed as a single array that prepare() accepts but the sniff cannot count statically.
            $rows = $wpdb->get_col(
                $wpdb->prepare(
                    "SELECT result_ids
                    FROM %i
                    WHERE filter_id = %d
                    AND field_name = %s
                    AND field_value IN ({$placeholders})",
                    $query_values
                )
            );
            // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber

            if (empty($rows)) {
                return array();
            }

            // Merge matching values: OR within the same field.
            $ids = array();

            foreach ($rows as $csv) {
                $ids = array_merge(
                    $ids,
                    explode(',', $csv)
                );
            }

            $ids = array_values(
                array_unique(
                    array_filter(
                        array_map('intval', $ids),
                        static function ($id) {
                            return $id > 0;
                        }
                    )
                )
            );

            if (empty($ids)) {
                return array();
            }

            $result_sets[] = $ids;
        } elseif (0 === strpos($key, ASKAGENTO_NUM_PREFIX)) {
            // Range filters use the cache table.
            $field_name = $key;

            list($range_min, $range_max) = askagento_parse_range_filter_bounds(
                $value
            );

            if (null === $range_min && null === $range_max) {
                continue;
            }

            list($global_min, $global_max) = askagento_cache_numeric_field_bounds(
                $filter_id,
                $field_name
            );

            if (null !== $global_min && null !== $global_max) {
                $covers_min = (
                    null === $range_min
                    || $range_min <= $global_min + 0.000001
                );

                $covers_max = (
                    null === $range_max
                    || $range_max >= $global_max - 0.000001
                );

                if ($covers_min && $covers_max) {
                    // Full-span range should not constrain the results.
                    continue;
                }
            }

            if (null !== $range_min && null !== $range_max) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query required to fetch matching post IDs from the plugin's custom cache table for a bounded numeric range filter (min and max).
                $ids = $wpdb->get_col(
                    $wpdb->prepare(
                        'SELECT DISTINCT post_id
                        FROM %i
                        WHERE filter_id = %d
                        AND field_name = %s
                        AND field_value_num >= %f
                        AND field_value_num <= %f',
                        $cache_table,
                        $filter_id,
                        $field_name,
                        $range_min,
                        $range_max
                    )
                );
            } elseif (null !== $range_min) {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query required to fetch matching post IDs from the plugin's custom cache table for a numeric range filter with a minimum bound only.
                $ids = $wpdb->get_col(
                    $wpdb->prepare(
                        'SELECT DISTINCT post_id
                        FROM %i
                        WHERE filter_id = %d
                        AND field_name = %s
                        AND field_value_num >= %f',
                        $cache_table,
                        $filter_id,
                        $field_name,
                        $range_min
                    )
                );
            } else {
                // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query required to fetch matching post IDs from the plugin's custom cache table for a numeric range filter with a maximum bound only.
                $ids = $wpdb->get_col(
                    $wpdb->prepare(
                        'SELECT DISTINCT post_id
                        FROM %i
                        WHERE filter_id = %d
                        AND field_name = %s
                        AND field_value_num <= %f',
                        $cache_table,
                        $filter_id,
                        $field_name,
                        $range_max
                    )
                );
            }

            if (empty($ids)) {
                if (
                    !askagento_cache_has_numeric_rows_for_field(
                        $filter_id,
                        $field_name
                    )
                ) {
                    continue;
                }

                return array();
            }

            $result_sets[] = array_map('intval', $ids);
        }
    }

    if (empty($result_sets)) {
        return null;
    }

    if (function_exists('askagento_normalize_to_catalog_product_ids')) {
        foreach ($result_sets as $index => $result_set) {
            $result_sets[$index] =
                askagento_normalize_to_catalog_product_ids($result_set);
        }
    }

    // Intersect all result sets: AND between different fields.
    $final = $result_sets[0];

    for ($index = 1; $index < count($result_sets); $index++) {
        $final = array_intersect(
            $final,
            $result_sets[$index]
        );
    }

    return array_values($final);
}

/**
 * Whether askagento_cache has any numeric rows for this filter field (used for range facets).
 */
function askagento_cache_has_numeric_rows_for_field($filter_id, $field_name) {
    global $wpdb;

    $filter_id = (int) $filter_id;
    if ($filter_id <= 0 || $field_name === '') {
        return false;
    }

    $cache_table = $wpdb->prefix . 'askagento_cache';
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query required to check whether the plugin's custom cache table holds any numeric rows for this filter field before applying range constraints.
    $n = (int) $wpdb->get_var(
        $wpdb->prepare(
            'SELECT COUNT(*) FROM %i WHERE filter_id = %d AND field_name = %s AND field_value_num IS NOT NULL',
            $cache_table,
            $filter_id,
            $field_name
        )
    );

    return $n > 0;
}
