<?php
/**
 * Language-aware WP_Query adjustments for front-end requests.
 *
 * This module translates query arguments and results so archives, widgets, and
 * other template queries surface the correct language variant of posts and terms.
 *
 * @package WPR_Translate
 */

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

/**
 * Log query filter activity when debugging is enabled.
 *
 * @param string $message Message to log.
 * @param array  $context Optional context data.
 *
 * @return void
 */
function wpr_translate_query_filter_log( $message, $context = array() ) {
    // Logging disabled to avoid writing into the global debug log.
    return;
}

/**
 * Register hooks that align queries with the visitor's language.
 *
 * @return void
 */
function wpr_translate_bootstrap_query_filters() {
    // Prevent duplicate hook registration when the bootstrap is called twice.
    static $registered = false;

    if ( $registered ) {
        // Hooks were already registered.
        return;
    }

    // Attach the hooks that adjust queries before and after execution.
    $registered = true;
    add_action( 'pre_get_posts', 'wpr_translate_filter_frontend_query', 9 );
    add_filter( 'the_posts', 'wpr_translate_filter_posts_results', 10, 2 );
}

/**
 * Adjust query vars before WordPress hits the database.
 *
 * @param WP_Query $query Active query object.
 *
 * @return void
 */
function wpr_translate_filter_frontend_query( $query ) {
    // Only operate on front-end queries that support filtering.
    if ( ! $query instanceof WP_Query || ( is_admin() && ! wp_doing_ajax() ) || wp_doing_cron() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
        return;
    }

    // Inspect the query early so we can identify estate_property usage.
    $post_type = $query->get( 'post_type' );
    $fields    = $query->get( 'fields' );

    if ( empty( $post_type ) ) {
        $post_types = array( 'post' );
    } elseif ( 'any' === $post_type ) {
        $post_types = get_post_types( array( 'public' => true ), 'names' );
    } elseif ( is_array( $post_type ) ) {
        $post_types = $post_type;
    } else {
        $post_types = array( $post_type );
    }

    $post_types = array_values( array_filter( array_map( 'sanitize_key', (array) $post_types ) ) );

    $skip_types = array( 'elementor_library', 'nav_menu_item' );
    $post_types = array_values( array_diff( $post_types, $skip_types ) );

    if ( empty( $post_types ) ) {
        // Skip filtering for non-content post types such as Elementor templates or menu items.
        return;
    }
    $is_pin_query = ( 'ids' === $fields && in_array( 'estate_property', $post_types, true ) );

    // Capture language context for pin queries (used for logging and filtering).
    $language     = '';
    $default_code = '';

    if ( $is_pin_query ) {
        // Resolve the request language now so the pin log includes the active language.
        $language = wpr_translate_get_request_language_code();

        if ( function_exists( 'wpr_translation_get_active_languages' ) && function_exists( 'wpr_translation_get_default_language_code' ) ) {
            $languages    = wpr_translation_get_active_languages();
            $default_code = sanitize_key( wpr_translation_get_default_language_code( $languages ) );
        }

        // Snapshot query inputs that matter for pin filtering decisions.
        $tax_query  = $query->get( 'tax_query' );
        $meta_query = $query->get( 'meta_query' );
        $post__in   = $query->get( 'post__in' );

        wpr_translate_query_filter_log(
            'Pin query detected.',
            array(
                'language'        => $language,
                'default_lang'    => $default_code,
                'is_default'      => ( '' !== $default_code && $language === $default_code ),
                'post_type'       => $post_type,
                'fields'          => $fields,
                'posts_per_page'  => $query->get( 'posts_per_page' ),
                'paged'           => $query->get( 'paged' ),
                'orderby'         => $query->get( 'orderby' ),
                'order'           => $query->get( 'order' ),
                'tax_query_count' => is_array( $tax_query ) ? count( $tax_query ) : 0,
                'meta_query_count'=> is_array( $meta_query ) ? count( $meta_query ) : 0,
                'post__in_count'  => is_array( $post__in ) ? count( $post__in ) : 0,
                'is_ajax'         => wp_doing_ajax() ? 1 : 0,
            )
        );
    }

    // Respect queries that explicitly suppress filters to avoid unintended changes.
    if ( $query->get( 'suppress_filters' ) ) {
        return;
    }

    // Resolve the language code that should apply to this query (if not already resolved).
    if ( '' === $language ) {
        $language = wpr_translate_get_request_language_code();
    }

    if ( '' === $language ) {
        // No language means no adjustments.
        return;
    }

    // Translate post identifiers before execution.
    wpr_translate_adjust_query_post_arguments( $query, $language );

    // Resolve meta_query post IDs to the default language first.  When a
    // meta value is adjusted the query will run against source-language rows,
    // so taxonomy terms must also stay in source language ??? the_posts filter
    // handles swapping both posts and terms to the visitor language afterward.
    $meta_adjusted = wpr_translate_adjust_query_meta( $query, $language );

    if ( ! $meta_adjusted ) {
        wpr_translate_adjust_query_taxonomies( $query, $language );
    }

    // Apply language-specific filtering to translatable post types.
    if ( ! empty( $post_types ) && function_exists( 'wpr_translation_get_active_languages' ) && function_exists( 'wpr_translation_get_default_language_code' ) ) {
        $languages    = wpr_translation_get_active_languages();
        $default_code = sanitize_key( wpr_translation_get_default_language_code( $languages ) );
        $language     = sanitize_key( $language );

        if ( '' !== $default_code ) {
            // When viewing a singular post without a translation, avoid filtering it out.
            $skip_language_filter = false;
            $singular_id          = 0;

            if ( $query->get( 'p' ) ) {
                $singular_id = absint( $query->get( 'p' ) );
            } elseif ( $query->get( 'page_id' ) ) {
                $singular_id = absint( $query->get( 'page_id' ) );
            } else {
                $singular_slug = $query->get( 'pagename' );

                if ( ! $singular_slug ) {
                    $singular_slug = $query->get( 'name' );
                }

                if ( $singular_slug ) {
                    $lookup_types = is_array( $post_types ) ? $post_types : array( $post_types );
                    $lookup_types = array_values( array_filter( array_map( 'sanitize_key', (array) $lookup_types ) ) );

                    if ( empty( $lookup_types ) ) {
                        $lookup_types = array( 'post', 'page' );
                    }

                    if ( ! in_array( 'page', $lookup_types, true ) ) {
                        // Ensure pages can be resolved when post_type is unset.
                        $lookup_types[] = 'page';
                    }

                    foreach ( $lookup_types as $lookup_type ) {
                        $singular_post = get_page_by_path( sanitize_title( $singular_slug ), OBJECT, $lookup_type );

                        if ( $singular_post instanceof WP_Post ) {
                            $singular_id = (int) $singular_post->ID;
                            break;
                        }
                    }
                }
            }

            if ( $singular_id ) {
                $translated_id = wpr_translate_resolve_translated_post_id( $singular_id, $language );

                if ( (int) $translated_id === (int) $singular_id ) {
                    $skip_language_filter = true;
                }
            }

            if ( $skip_language_filter ) {
                // Skip language filtering so the original post can render.
                $query->set( 'wpr_translate_adjust_results', true );
                return;
            }

            $element_types = array();

            foreach ( $post_types as $type ) {
                if ( '' === $type ) {
                    continue;
                }

                if ( function_exists( 'wpr_translation_get_element_type' ) ) {
                    $element_types[] = wpr_translation_get_element_type( $type );
                } else {
                    $element_types[] = 'post_' . $type;
                }
            }

            $element_types = array_values( array_filter( array_unique( array_map( 'sanitize_key', $element_types ) ) ) );

            if ( ! empty( $element_types ) ) {
                global $wpdb;

                $placeholders = implode( ',', array_fill( 0, count( $element_types ), '%s' ) );
                $table        = $wpdb->prefix . 'wpestate_translation_translations';

                if ( $language !== $default_code ) {
                    $sql    = "SELECT element_id FROM {$table} WHERE element_type IN ({$placeholders}) AND language_code = %s";
                    $params = array_merge( $element_types, array( $language ) );
                    $prepared = call_user_func_array( array( $wpdb, 'prepare' ), array_merge( array( $sql ), $params ) );
                    $allowed_ids = $wpdb->get_col( $prepared );

                    $allowed_ids   = array_map( 'intval', (array) $allowed_ids );
                    $allowed_total = count( $allowed_ids );
                    $has_translations = ( $allowed_total > 0 );

                    if ( ! $has_translations ) {
                        $sql_any      = "SELECT element_id FROM {$table} WHERE element_type IN ({$placeholders}) LIMIT 1";
                        $prepared_any = call_user_func_array( array( $wpdb, 'prepare' ), array_merge( array( $sql_any ), $element_types ) );
                        $has_translations = (bool) $wpdb->get_var( $prepared_any );
                    }

                    // Intersect with any existing allow-list from the theme query.
                    $existing_ids   = $query->get( 'post__in' );
                    $existing_count = is_array( $existing_ids ) ? count( $existing_ids ) : 0;

                    if ( is_array( $existing_ids ) && ! empty( $existing_ids ) ) {
                        $allowed_ids = array_values( array_intersect( $allowed_ids, array_map( 'intval', $existing_ids ) ) );
                    }

                    // Force an empty result set when no translated listings match.
                    if ( empty( $allowed_ids ) ) {
                        if ( $has_translations ) {
                            $allowed_ids = array( 0 );
                        } else {
                            wpr_translate_query_filter_log(
                                'Query language filter skipped due to missing translations.',
                                array(
                                    'language'     => $language,
                                    'default_lang' => $default_code,
                                    'post_type'    => $post_types,
                                )
                            );
                        }
                    }

                    if ( ! empty( $allowed_ids ) ) {
                        $query->set( 'post__in', $allowed_ids );
                    }

                    wpr_translate_query_filter_log(
                        'Query language filter applied.',
                        array(
                            'language'       => $language,
                            'default_lang'   => $default_code,
                            'allowed_total'  => $allowed_total,
                            'existing_count' => $existing_count,
                            'final_count'    => count( $allowed_ids ),
                        )
                    );
                } else {
                    $sql    = "SELECT element_id FROM {$table} WHERE element_type IN ({$placeholders}) AND language_code != %s";
                    $params = array_merge( $element_types, array( $default_code ) );
                    $prepared = call_user_func_array( array( $wpdb, 'prepare' ), array_merge( array( $sql ), $params ) );
                    $excluded_ids = $wpdb->get_col( $prepared );

                    $excluded_ids   = array_map( 'intval', (array) $excluded_ids );
                    $excluded_total = count( $excluded_ids );

                    // Merge with any pre-existing exclusions from the theme.
                    $existing_excluded = $query->get( 'post__not_in' );

                    if ( is_array( $existing_excluded ) && ! empty( $existing_excluded ) ) {
                        $excluded_ids = array_merge( $excluded_ids, array_map( 'intval', $existing_excluded ) );
                    }

                    $excluded_ids = array_values( array_unique( $excluded_ids ) );

                    // Apply the exclusion list only when there is something to exclude.
                    if ( ! empty( $excluded_ids ) ) {
                        $query->set( 'post__not_in', $excluded_ids );
                    }

                    wpr_translate_query_filter_log(
                        'Query default language exclusion applied.',
                        array(
                            'language'       => $language,
                            'default_lang'   => $default_code,
                            'excluded_total' => $excluded_total,
                            'final_count'    => count( $excluded_ids ),
                        )
                    );
                }
            }
        }
    }

    // Remember that this query requires result-level adjustments later on.
    $query->set( 'wpr_translate_adjust_results', true );
}
function wpr_translate_filter_posts_results( $posts, $query ) {
    if ( ! $query instanceof WP_Query || empty( $posts ) ) {
        return $posts;
    }

    // Identify pin queries so we can apply ID-level filtering even without wpr_translate_adjust_results.
    $post_type      = $query->get( 'post_type' );
    $is_pin_query   = false;
    $has_ids_fields = ( 'ids' === $query->get( 'fields' ) );

    if ( $has_ids_fields ) {
        if ( is_array( $post_type ) ) {
            $is_pin_query = in_array( 'estate_property', $post_type, true );
        } else {
            $is_pin_query = ( 'estate_property' === $post_type );
        }
    }

    if ( ! $query->get( 'wpr_translate_adjust_results' ) && ! $is_pin_query ) {
        // Skip non-targeted queries for performance.
        return $posts;
    }

    // Resolve the request language for post replacement or ID translation.
    $language = wpr_translate_get_request_language_code();

    if ( '' === $language ) {
        // Without a language code, keep the original results.
        return $posts;
    }

    if ( is_array( $posts ) && isset( $posts[0] ) && ( is_int( $posts[0] ) || is_string( $posts[0] ) ) ) {
        // Translate ID-only result sets (fields=ids) to the current language.
        $translated_ids = array();

        foreach ( $posts as $post_id ) {
            $post_id = (int) $post_id;

            if ( $post_id <= 0 ) {
                continue;
            }

            $translated = wpr_translate_resolve_translated_post_id( $post_id, $language );

            if ( $translated ) {
                $translated_ids[] = (int) $translated;
            } else {
                $translated_ids[] = $post_id;
            }
        }

        $translated_ids = array_values( array_unique( $translated_ids ) );

        // Only apply language filtering for estate_property IDs.
        $target_post_type = '';

        if ( is_array( $post_type ) ) {
            if ( in_array( 'estate_property', $post_type, true ) ) {
                $target_post_type = 'estate_property';
            }
        } elseif ( 'estate_property' === $post_type ) {
            $target_post_type = 'estate_property';
        }

        if ( 'ids' === $query->get( 'fields' ) && '' !== $target_post_type && function_exists( 'wpr_translation_resolve_post_language_code' ) ) {
            $languages    = function_exists( 'wpr_translation_get_active_languages' ) ? wpr_translation_get_active_languages() : array();
            $default_code = '';

            if ( ! empty( $languages ) && function_exists( 'wpr_translation_get_default_language_code' ) ) {
                $default_code = strtolower( sanitize_key( wpr_translation_get_default_language_code( $languages ) ) );
            }

            // Keep unmapped properties only when browsing the default language.
            $target_language = strtolower( sanitize_key( $language ) );
            $allow_unmapped  = ( '' !== $default_code && $default_code === $target_language );
            $filtered_ids    = array();

            foreach ( $translated_ids as $translated_id ) {
                $post_language = wpr_translation_resolve_post_language_code( (int) $translated_id, $target_post_type );
                $post_language = strtolower( sanitize_key( $post_language ) );

                if ( '' === $post_language ) {
                    // Unmapped post: only keep it for the default language.
                    if ( $allow_unmapped ) {
                        $filtered_ids[] = (int) $translated_id;
                    }
                    continue;
                }

                if ( $post_language === $target_language ) {
                    // Keep posts whose stored language matches the request language.
                    $filtered_ids[] = (int) $translated_id;
                }
            }

            $filtered_ids = array_values( array_unique( $filtered_ids ) );

            if ( $is_pin_query ) {
                // Log the ID filtering outcome to verify pin behavior.
                wpr_translate_query_filter_log(
                    'Pin query results filtered.',
                    array(
                        'language'   => $language,
                        'post_type'  => $target_post_type,
                        'original'   => count( $posts ),
                        'translated' => count( $translated_ids ),
                        'filtered'   => count( $filtered_ids ),
                    )
                );
            }

            return $filtered_ids;
        }

        return $translated_ids;
    }

    $replaced = 0;

    foreach ( $posts as $index => $post ) {
        if ( ! $post instanceof WP_Post ) {
            // Skip non-post items.
            continue;
        }

        // Resolve the best match for the visitor language and swap the object.
        $replacement_id = wpr_translate_resolve_translated_post_id( $post->ID, $language );

        if ( $replacement_id && $replacement_id !== $post->ID ) {
            $replacement = get_post( $replacement_id );

            if ( $replacement instanceof WP_Post ) {
                // Swap in the translated post object.
                $posts[ $index ] = $replacement;
                $replaced++;
            }
        }
    }

    if ( $replaced > 0 ) {
        // De-dupe translated results by post ID to avoid double renders.
        $unique = array();
        $seen   = array();

        foreach ( $posts as $post ) {
            if ( ! $post instanceof WP_Post ) {
                continue;
            }

            if ( isset( $seen[ $post->ID ] ) ) {
                continue;
            }

            $seen[ $post->ID ] = true;
            $unique[]          = $post;
        }

        $unique_count = count( $unique );

        // Keep the query counters aligned when all posts are loaded.
        $posts_per_page = (int) $query->get( 'posts_per_page' );
        $found_posts    = isset( $query->found_posts ) ? (int) $query->found_posts : 0;
        $all_loaded     = ( -1 === $posts_per_page || ( $found_posts > 0 && $posts_per_page >= $found_posts ) );

        if ( $all_loaded && $found_posts > $unique_count ) {
            $query->found_posts   = $unique_count;
            $query->post_count    = $unique_count;
            $query->max_num_pages = 1;
        } else {
            $query->post_count = $unique_count;
        }

        return $unique;
    }

    return $posts;
}

/**
 * Resolve the language code that should apply to the current request.
 *
 * @return string Normalized language code or empty string when unknown.
 */
function wpr_translate_get_request_language_code() {
    global $wpr_translate_current_language;

    // For AJAX requests, honor an explicit language parameter when provided.
    if ( wp_doing_ajax() && function_exists( 'wpr_translate_get_language' ) ) {
        $requested_lang = isset( $_REQUEST['lang'] ) ? sanitize_key( wp_unslash( $_REQUEST['lang'] ) ) : '';

        if ( '' !== $requested_lang ) {
            $resolved = wpr_translate_get_language( $requested_lang );

            if ( is_array( $resolved ) ) {
                if ( ! empty( $resolved['code'] ) ) {
                    return sanitize_key( $resolved['code'] );
                }

                if ( ! empty( $resolved['slug'] ) ) {
                    return sanitize_key( $resolved['slug'] );
                }
            }
        }
    }

    if ( ! empty( $wpr_translate_current_language ) && is_array( $wpr_translate_current_language ) ) {
        // Normalize the cached language for consistent access.
        $language = function_exists( 'wpr_translate_normalize_language' )
            ? wpr_translate_normalize_language( $wpr_translate_current_language )
            : $wpr_translate_current_language;

        if ( ! empty( $language['code'] ) ) {
            // Prefer the language code for translation lookups.
            return sanitize_key( $language['code'] );
        }

        if ( function_exists( 'wpr_translate_get_language_slug' ) ) {
            $slug = wpr_translate_get_language_slug( $language );

            if ( '' !== $slug && function_exists( 'wpr_translate_get_language' ) ) {
                // Fall back to resolving the code from the slug.
                $resolved = wpr_translate_get_language( $slug );

                if ( is_array( $resolved ) && ! empty( $resolved['code'] ) ) {
                    return sanitize_key( $resolved['code'] );
                }
            }
        }
    }

    // The WPResidence helper returns the active language code when set.
    if ( function_exists( 'wpestate_get_current_language' ) ) {
        // Use the legacy theme helper when present.
        $helper_language = sanitize_key( wpestate_get_current_language() );

        if ( '' !== $helper_language ) {
            if ( function_exists( 'wpr_translate_get_language' ) ) {
                $resolved = wpr_translate_get_language( $helper_language );

                if ( is_array( $resolved ) && ! empty( $resolved['code'] ) ) {
                    return sanitize_key( $resolved['code'] );
                }
            }

            return $helper_language;
        }
    }

    // For AJAX requests, attempt to resolve the language from the HTTP referrer path.
    if ( wp_doing_ajax() && function_exists( 'wpr_translate_get_language' ) ) {
        $referer = wp_get_referer();

        if ( $referer ) {
            $parts = wp_parse_url( $referer );
            $path  = isset( $parts['path'] ) ? (string) $parts['path'] : '';

            if ( '' !== $path ) {
                $base_path = wp_parse_url( home_url(), PHP_URL_PATH );
                $base_path = is_string( $base_path ) ? rtrim( $base_path, '/' ) : '';

                if ( '' !== $base_path && '/' !== $base_path && 0 === strpos( $path, $base_path ) ) {
                    $path = substr( $path, strlen( $base_path ) );
                }

                $segments = array_values( array_filter( explode( '/', trim( $path, '/' ) ) ) );
                $first    = isset( $segments[0] ) ? strtolower( (string) $segments[0] ) : '';

                if ( '' !== $first ) {
                    $resolved = wpr_translate_get_language( $first );

                    if ( is_array( $resolved ) ) {
                        if ( ! empty( $resolved['code'] ) ) {
                            return sanitize_key( $resolved['code'] );
                        }

                        if ( ! empty( $resolved['slug'] ) ) {
                            return sanitize_key( $resolved['slug'] );
                        }
                    }
                }
            }
        }
    }

    // For AJAX requests, fall back to the provided post context when available.
    if ( wp_doing_ajax() && function_exists( 'wpr_translate_get_post_language' ) ) {
        $context_post_id = isset( $_POST['post_id'] ) ? absint( wp_unslash( $_POST['post_id'] ) ) : 0;

        if ( $context_post_id ) {
            $context_language = wpr_translate_get_post_language( $context_post_id );

            if ( is_array( $context_language ) && ! empty( $context_language['code'] ) ) {
                return sanitize_key( $context_language['code'] );
            }
        }
    }

    // Use the preferred language (cookie/browser) when available.
    if ( function_exists( 'wpr_translate_resolve_preferred_language' ) ) {
        $preferred = wpr_translate_resolve_preferred_language();

        if ( is_array( $preferred ) ) {
            if ( ! empty( $preferred['code'] ) ) {
                return sanitize_key( $preferred['code'] );
            }

            if ( ! empty( $preferred['slug'] ) ) {
                return sanitize_key( $preferred['slug'] );
            }
        }
    }

    // Fall back to the plugin defaults when the helper is unavailable.
    if ( ! function_exists( 'wpr_translation_get_active_languages' ) || ! function_exists( 'wpr_translation_get_default_language_code' ) ) {
        return '';
    }

    $languages = wpr_translation_get_active_languages();

    if ( empty( $languages ) ) {
        // No active languages configured.
        return '';
    }

    $default_code = wpr_translation_get_default_language_code( $languages );

    if ( '' !== $default_code ) {
        // Use the default language code.
        return sanitize_key( $default_code );
    }

    return '';
}

/**
 * Translate post-related query vars (IDs and slugs).
 *
 * @param WP_Query $query    Query being processed.
 * @param string   $language Visitor language code.
 *
 * @return void
 */
function wpr_translate_adjust_query_post_arguments( $query, $language ) {
    // Iterate through scalar identifiers and translate them one by one.
    foreach ( array( 'p', 'page_id', 'post_parent', 'attachment_id' ) as $single_key ) {
        $value = $query->get( $single_key );

        // Skip empty parameters so we avoid altering unrelated queries.
        if ( ! $value ) {
            // Skip missing values.
            continue;
        }

        // Convert the referenced post ID to the translated sibling.
        $incoming_id = (int) $value;
        $translated  = wpr_translate_resolve_translated_post_id( $incoming_id, $language );
        $final_id    = $incoming_id;

        if ( $translated && $translated !== $incoming_id ) {
            // Replace the ID with the translated post.
            $query->set( $single_key, $translated );
            $final_id = (int) $translated;
        }

    }

    // Translate any lists of post IDs used to include or exclude content.
    foreach ( array( 'post__in', 'post_parent__in', 'post__not_in', 'post_parent__not_in' ) as $list_key ) {
        $list = $query->get( $list_key );

        // Ignore empty or malformed lists to keep the original behaviour.
        if ( empty( $list ) || ! is_array( $list ) ) {
            continue;
        }

        $translated = array();

        foreach ( $list as $post_id ) {
            // Preserve ordering while replacing each ID with the current-language post.
            $translated[] = wpr_translate_resolve_translated_post_id( (int) $post_id, $language );
        }

        // Store the translated list in the query.
        $query->set( $list_key, array_filter( array_map( 'absint', $translated ) ) );
    }

    // Convert slug-based lookups so archives resolve to translated posts.
    foreach ( array( 'name', 'pagename' ) as $slug_key ) {
        $slug = $query->get( $slug_key );

        // Skip when no slug is provided for this query variable.
        if ( ! $slug ) {
            continue;
        }

        // Replace the slug with the translated post name if one exists.
        $translated_slug = wpr_translate_lookup_translated_post_slug( $slug, $language, $query );

        if ( '' !== $translated_slug && $translated_slug !== $slug ) {
            // Replace the slug with its translated version.
            $query->set( $slug_key, $translated_slug );
        }
    }
}

/**
 * Translate taxonomy constraints so lookups honour the visitor language.
 *
 * @param WP_Query $query    Query being processed.
 * @param string   $language Visitor language code.
 *
 * @return void
 */
function wpr_translate_adjust_query_taxonomies( $query, $language ) {
    // Translate tax_query clauses when present.
    $tax_query = $query->get( 'tax_query' );

    if ( is_array( $tax_query ) ) {
        foreach ( $tax_query as $index => $clause ) {
            if ( empty( $clause['taxonomy'] ) || empty( $clause['terms'] ) ) {
                continue;
            }

            $taxonomy = sanitize_key( $clause['taxonomy'] );
            $field    = isset( $clause['field'] ) ? strtolower( (string) $clause['field'] ) : 'term_id';
            $terms    = (array) $clause['terms'];

            // Translate the clause terms based on the provided lookup field.
            $clause['terms'] = wpr_translate_translate_taxonomy_terms( $terms, $taxonomy, $field, $language );
            $tax_query[ $index ] = $clause;
        }

        // Update the query with translated taxonomy constraints.
        $query->set( 'tax_query', $tax_query );
    }

    // Also handle shorthand category/tag query vars used by core templates.
    $shorthand = array(
        'cat'              => array( 'taxonomy' => 'category', 'field' => 'term_id' ),
        'category__in'     => array( 'taxonomy' => 'category', 'field' => 'term_id' ),
        'category__not_in' => array( 'taxonomy' => 'category', 'field' => 'term_id' ),
        'category__and'    => array( 'taxonomy' => 'category', 'field' => 'term_id' ),
        'tag__in'          => array( 'taxonomy' => 'post_tag', 'field' => 'term_id' ),
        'tag__not_in'      => array( 'taxonomy' => 'post_tag', 'field' => 'term_id' ),
        'tag__and'         => array( 'taxonomy' => 'post_tag', 'field' => 'term_id' ),
        'tag_slug__in'     => array( 'taxonomy' => 'post_tag', 'field' => 'slug' ),
        'tag_slug__and'    => array( 'taxonomy' => 'post_tag', 'field' => 'slug' ),
    );

    foreach ( $shorthand as $var => $details ) {
        $value = $query->get( $var );

        // Skip empty shorthand clauses to avoid overriding default behaviour.
        if ( empty( $value ) ) {
            continue;
        }

        // Translate the stored values using the appropriate lookup field.
        $translated = wpr_translate_translate_taxonomy_terms( (array) $value, $details['taxonomy'], $details['field'], $language );
        // Save the translated shorthand values back to the query.
        $query->set( $var, $translated );
    }
}

/**
 * Translate post-ID values inside meta_query so lookups like property_agent
 * resolve against the source-language post that the meta was originally written for.
 *
 * When a property is created in the default language its meta fields such as
 * property_agent store the source-language agent post ID.  On a translated
 * language page the theme passes the translated agent's $post->ID into the
 * meta_query, which never matches any property row.  This helper resolves
 * each numeric meta value back to its default-language post so the query
 * returns the expected rows.
 *
 * @param WP_Query $query    Query being processed.
 * @param string   $language Visitor language code.
 *
 * @return bool True when at least one meta value was translated.
 */
function wpr_translate_adjust_query_meta( $query, $language ) {
    $meta_query = $query->get( 'meta_query' );

    if ( ! is_array( $meta_query ) ) {
        return false;
    }

    if ( ! function_exists( 'wpr_translation_get_active_languages' ) || ! function_exists( 'wpr_translation_get_default_language_code' ) ) {
        return false;
    }

    $languages    = wpr_translation_get_active_languages();
    $default_code = strtolower( sanitize_key( wpr_translation_get_default_language_code( $languages ) ) );
    $current_code = strtolower( sanitize_key( $language ) );

    // Nothing to do when the visitor is already browsing the default language.
    if ( '' === $default_code || $default_code === $current_code ) {
        return false;
    }

    $changed = false;

    foreach ( $meta_query as $index => $clause ) {
        // Skip the top-level relation key and any malformed entries.
        if ( ! is_array( $clause ) || ! isset( $clause['key'] ) || ! isset( $clause['value'] ) ) {
            continue;
        }

        // Booking calendar queries must keep their original listing ID so
        // wpestate_get_booking_dates() aggregates bookings for the exact
        // listing being updated.
        if ( wpr_translate_should_skip_meta_query_id_translation( $query, $clause ) ) {
            continue;
        }

        $value = $clause['value'];

        // Only translate single scalar numeric values.  Array values (used
        // with IN / NOT IN) and non-numeric strings are left alone.
        if ( is_array( $value ) || ! is_numeric( $value ) ) {
            continue;
        }

        $int_value = absint( $value );

        if ( ! $int_value ) {
            continue;
        }

        // Resolve toward the default language.  The helper returns 0 for
        // non-existent posts and the original ID when no translation group
        // exists, so prices and other incidental numeric meta values pass
        // through unchanged.
        $resolved = wpr_translate_resolve_translated_post_id( $int_value, $default_code );

        if ( $resolved && $resolved !== $int_value ) {
            $clause['value']      = $resolved;
            $meta_query[ $index ] = $clause;
            $changed              = true;

            wpr_translate_query_filter_log(
                'Translated meta_query post ID to default language.',
                array(
                    'key'          => $clause['key'],
                    'original_id'  => $int_value,
                    'resolved_id'  => $resolved,
                    'default_lang' => $default_code,
                )
            );
        }
    }

    if ( $changed ) {
        $query->set( 'meta_query', $meta_query );
    }

    return $changed;
}

/**
 * Determine whether a meta_query clause should skip post-ID translation.
 *
 * Keep booking calendar lookups untouched:
 * - post_type contains `wpestate_booking`
 * - meta_query key is `booking_id`
 *
 * @param WP_Query $query  Query being processed.
 * @param array    $clause Single meta_query clause.
 *
 * @return bool
 */
function wpr_translate_should_skip_meta_query_id_translation( $query, $clause ) {
    if ( ! $query instanceof WP_Query || ! is_array( $clause ) ) {
        return false;
    }

    $meta_key = '';

    if ( isset( $clause['key'] ) ) {
        $meta_key = strtolower( sanitize_key( (string) $clause['key'] ) );
    }

    if ( 'booking_id' !== $meta_key ) {
        return false;
    }

    $post_type = $query->get( 'post_type' );
    $post_types = array();

    if ( empty( $post_type ) ) {
        $post_types = array( 'post' );
    } elseif ( 'any' === $post_type ) {
        $post_types = get_post_types( array( 'public' => true ), 'names' );

        if ( post_type_exists( 'wpestate_booking' ) && ! in_array( 'wpestate_booking', $post_types, true ) ) {
            $post_types[] = 'wpestate_booking';
        }
    } elseif ( is_array( $post_type ) ) {
        $post_types = $post_type;
    } else {
        $post_types = array( $post_type );
    }

    $post_types = array_values( array_filter( array_map( 'sanitize_key', (array) $post_types ) ) );

    return in_array( 'wpestate_booking', $post_types, true );
}
