<?php
/**
 * Helper functions supporting the front-end query filters.
 *
 * These utilities translate post and term identifiers to the visitor's language
 * so archive and widget queries surface the expected localized content.
 *
 * @package WPR_Translate
 */

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

/**
 * Log translation lookup activity when debugging is enabled.
 *
 * @param string $message Message to log.
 * @param array  $context Optional context data.
 *
 * @return void
 */
function wpr_translate_query_lookup_log( $message, $context = array() ) {
    // Logging disabled.
    return;
}

/**
 * Translate taxonomy term identifiers for the requested language.
 *
 * @param array  $terms    Term values (IDs or slugs) taken from the query.
 * @param string $taxonomy Taxonomy slug associated with the terms.
 * @param string $field    Lookup field (`term_id` or `slug`).
 * @param string $language Language code that should be preferred.
 *
 * @return array Translated term identifiers suitable for WP_Query arguments.
 */
function wpr_translate_translate_taxonomy_terms( $terms, $taxonomy, $field, $language ) {
    // Build a translated term list to replace the original query args.
    $translated = array();
    $taxonomy   = sanitize_key( $taxonomy );
    $field      = strtolower( sanitize_text_field( (string) $field ) );
    $language   = sanitize_key( $language );
    $terms      = is_array( $terms ) ? $terms : array();

    if ( '' === $taxonomy || empty( $terms ) || ! taxonomy_exists( $taxonomy ) ) {
        return $translated;
    }

    $default_language = '';

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

    $is_default_language = ( '' !== $default_language && $default_language === $language );

    foreach ( $terms as $term_value ) {
        $source_term_id = 0;

        if ( 'slug' === $field ) {
            // Look up the source term by slug before language resolution.
            $source_slug = sanitize_title( (string) $term_value );

            if ( '' === $source_slug ) {
                continue;
            }

            $source_term = get_term_by( 'slug', $source_slug, $taxonomy );

            if ( ! $source_term instanceof WP_Term ) {
                continue;
            }

            $source_term_id = (int) $source_term->term_id;
        } else {
            // Handle ID-based lookups by using the provided term ID.
            $source_term_id = absint( $term_value );
        }

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

        // Resolve canonical original term from term meta; missing reverse meta means original/default.
        $canonical_term_id = $source_term_id;

        if ( function_exists( 'wpr_translate_resolve_original_term_id' ) ) {
            $resolved_canonical = absint( wpr_translate_resolve_original_term_id( $source_term_id, $taxonomy ) );

            if ( $resolved_canonical > 0 ) {
                $canonical_term_id = $resolved_canonical;
            }
        }

        $target_term_id = 0;

        if ( $is_default_language || '' === $language ) {
            // In default language always keep canonical/original terms.
            $target_term_id = $canonical_term_id;
        } elseif ( function_exists( 'wpr_translate_lookup_translated_term_id_strict' ) ) {
            // In non-default language keep only explicit mappings.
            $resolved_target = wpr_translate_lookup_translated_term_id_strict( $canonical_term_id, $taxonomy, $language );

            if ( is_numeric( $resolved_target ) ) {
                $target_term_id = absint( $resolved_target );
            }
        }

        if ( $target_term_id <= 0 ) {
            // Drop unresolved mappings for non-default languages.
            continue;
        }

        if ( 'slug' === $field ) {
            $translated_term = get_term( $target_term_id, $taxonomy );

            if ( $translated_term instanceof WP_Term ) {
                $translated[] = sanitize_title( $translated_term->slug );
            }

            continue;
        }

        $translated[] = $target_term_id;
    }

    if ( 'slug' === $field ) {
        return array_values( array_unique( array_filter( $translated ) ) );
    }

    return array_values( array_unique( array_filter( array_map( 'absint', $translated ) ) ) );
}

/**
 * Resolve the best post ID for the requested language, using fallbacks.
 *
 * @param int    $post_id  Source post ID coming from the query.
 * @param string $language Target language code determined for the request.
 *
 * @return int Post ID that should replace the source ID.
 */
function wpr_translate_resolve_translated_post_id( $post_id, $language ) {
    // Cache lookups for the duration of the request to reduce database queries.
    static $cache = array();

    $post_id = absint( $post_id );

    if ( ! $post_id ) {
        // Invalid post ID.
        return 0;
    }

    $cache_key = $post_id . ':' . $language;

    if ( isset( $cache[ $cache_key ] ) ) {
        // Return cached lookup result.
        return $cache[ $cache_key ];
    }

    // Fetch the source post so we can inspect its post type and translation group.
    $post = get_post( $post_id );

    if ( ! $post instanceof WP_Post ) {
        // Cache missing posts as zero.
        $cache[ $cache_key ] = 0;
        return 0;
    }

    $group = wpr_translation_get_translation_group( $post_id, wpr_translation_get_element_type( $post->post_type ) );

    if ( empty( $group['trid'] ) || empty( $group['translations'] ) ) {
        // No translations available; keep the source ID.
        $cache[ $cache_key ] = $post_id;
        return $post_id;
    }

    $translations = $group['translations'];

    // Prefer a translation that matches the visitor language exactly.
    if ( ! empty( $translations[ $language ] ) && wpr_translation_is_translation_post_available( (int) $translations[ $language ] ) ) {
        // Use the translated post when it exists and is published.
        $cache[ $cache_key ] = (int) $translations[ $language ];
        if ( $cache[ $cache_key ] !== $post_id ) {
            wpr_translate_query_lookup_log(
                'Resolved translated post ID.',
                array(
                    'post_id'        => $post_id,
                    'language'       => $language,
                    'translated_id'  => $cache[ $cache_key ],
                    'source_language'=> isset( $group['source_language'] ) ? $group['source_language'] : '',
                )
            );
        }
        return $cache[ $cache_key ];
    }

    $source_language = isset( $group['source_language'] ) ? sanitize_key( $group['source_language'] ) : '';

    // Fall back to the recorded source language when an exact match is missing.
    if ( '' !== $source_language && ! empty( $translations[ $source_language ] ) && wpr_translation_is_translation_post_available( (int) $translations[ $source_language ] ) ) {
        // Use the source-language translation as a fallback.
        $cache[ $cache_key ] = (int) $translations[ $source_language ];
        if ( $cache[ $cache_key ] !== $post_id ) {
            wpr_translate_query_lookup_log(
                'Falling back to source-language post ID.',
                array(
                    'post_id'        => $post_id,
                    'language'       => $language,
                    'translated_id'  => $cache[ $cache_key ],
                    'source_language'=> $source_language,
                )
            );
        }
        return $cache[ $cache_key ];
    }

    // As a final fallback, keep the original post ID.
    $cache[ $cache_key ] = $post_id;
    return $post_id;
}

/**
 * Resolve the translated slug when queries match posts by name.
 *
 * @param string   $slug     Original slug provided to WP_Query.
 * @param string   $language Target language code determined for the request.
 * @param WP_Query $query    Current query (used to infer post type context).
 *
 * @return string Translated slug or the original value when unchanged.
 */
function wpr_translate_lookup_translated_post_slug( $slug, $language, $query ) {
    // Normalize the slug before lookup.
    $slug = sanitize_title( $slug );

    if ( '' === $slug ) {
        // Nothing to translate.
        return '';
    }

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

    if ( empty( $post_type ) ) {
        // Default to posts when no type is specified.
        $post_type = 'post';
    } elseif ( is_array( $post_type ) ) {
        $post_type = reset( $post_type );
    }

    // Resolve the source post that owns the provided slug.
    $source = get_page_by_path( $slug, OBJECT, $post_type );

    if ( ! $source instanceof WP_Post ) {
        // Return the original slug when no post is found.
        return $slug;
    }

    // Translate the source ID to the target language so we can fetch its slug.
    $translated_id = wpr_translate_resolve_translated_post_id( $source->ID, $language );

    if ( ! $translated_id || $translated_id === $source->ID ) {
        // No translation found, keep the original slug.
        return $slug;
    }

    $translated_slug = get_post_field( 'post_name', $translated_id );

    if ( '' === $translated_slug ) {
        // Preserve the original slug when the translated slug is empty.
        return $slug;
    }

    if ( $translated_slug !== $slug ) {
        // Log slug changes to help debug query translations.
        wpr_translate_query_lookup_log(
            'Resolved translated post slug.',
            array(
                'source_id'       => $source->ID,
                'language'        => $language,
                'source_slug'     => $slug,
                'translated_slug' => $translated_slug,
            )
        );
    }

    return sanitize_title( $translated_slug );
}

/**
 * Normalize requested language codes for term translation lookups.
 *
 * @param string $language Language code supplied by caller.
 *
 * @return string
 */
function wpr_translate_normalize_term_translation_language( $language ) {
    return strtolower( sanitize_key( (string) $language ) );
}

/**
 * Validate that a term ID exists and belongs to the requested taxonomy.
 *
 * @param int    $term_id  Term ID.
 * @param string $taxonomy Taxonomy slug.
 *
 * @return bool
 */
function wpr_translate_is_valid_term_for_taxonomy( $term_id, $taxonomy ) {
    $term_id  = absint( $term_id );
    $taxonomy = sanitize_key( $taxonomy );

    if ( $term_id <= 0 || '' === $taxonomy || ! taxonomy_exists( $taxonomy ) ) {
        return false;
    }

    $term = get_term( $term_id, $taxonomy );

    return ( $term instanceof WP_Term && ! is_wp_error( $term ) );
}

/**
 * Build the forward term-meta key for a translated language mapping.
 *
 * @param string $language Language code.
 *
 * @return string
 */
function wpr_translate_get_translated_term_meta_key( $language ) {
    return 'wpr_translated_term_' . wpr_translate_normalize_term_translation_language( $language );
}

/**
 * Resolve the original/source term ID for any term in a translation pair.
 *
 * @param int    $term_id  Input term ID.
 * @param string $taxonomy Taxonomy slug.
 *
 * @return int
 */
function wpr_translate_resolve_original_term_id( $term_id, $taxonomy ) {
    $term_id  = absint( $term_id );
    $taxonomy = sanitize_key( $taxonomy );

    if ( ! wpr_translate_is_valid_term_for_taxonomy( $term_id, $taxonomy ) ) {
        return $term_id;
    }

    $original_term_id = absint( get_term_meta( $term_id, 'wpr_original_term_id', true ) );

    if ( $original_term_id > 0 && wpr_translate_is_valid_term_for_taxonomy( $original_term_id, $taxonomy ) ) {
        return $original_term_id;
    }

    return $term_id;
}

/**
 * Read and validate the translated term ID stored in term meta.
 *
 * @param int    $original_term_id Original/source term ID.
 * @param string $taxonomy         Taxonomy slug.
 * @param string $language         Target language code.
 *
 * @return int
 */
function wpr_translate_read_translated_term_id_from_meta( $original_term_id, $taxonomy, $language ) {
    $original_term_id = absint( $original_term_id );
    $taxonomy         = sanitize_key( $taxonomy );
    $language         = wpr_translate_normalize_term_translation_language( $language );

    if ( $original_term_id <= 0 || '' === $taxonomy || '' === $language ) {
        return 0;
    }

    $translated_id = absint( get_term_meta( $original_term_id, wpr_translate_get_translated_term_meta_key( $language ), true ) );

    if ( $translated_id <= 0 || ! wpr_translate_is_valid_term_for_taxonomy( $translated_id, $taxonomy ) ) {
        return 0;
    }

    // Ignore broken legacy self-mappings (original term mapped to itself).
    if ( $translated_id === $original_term_id ) {
        return 0;
    }

    return $translated_id;
}

/**
 * Store bidirectional term translation mappings in term meta.
 *
 * @param int    $original_term_id   Original/source term ID.
 * @param int    $translated_term_id Translated term ID.
 * @param string $language           Target language code.
 * @param string $taxonomy           Taxonomy slug.
 *
 * @return bool
 */
function wpr_translate_link_term_translation_meta( $original_term_id, $translated_term_id, $language, $taxonomy ) {
    $original_term_id   = absint( $original_term_id );
    $translated_term_id = absint( $translated_term_id );
    $taxonomy           = sanitize_key( $taxonomy );
    $language           = wpr_translate_normalize_term_translation_language( $language );

    if (
        $original_term_id <= 0
        || $translated_term_id <= 0
        || '' === $taxonomy
        || '' === $language
        || $original_term_id === $translated_term_id
    ) {
        return false;
    }

    if (
        ! wpr_translate_is_valid_term_for_taxonomy( $original_term_id, $taxonomy )
        || ! wpr_translate_is_valid_term_for_taxonomy( $translated_term_id, $taxonomy )
    ) {
        return false;
    }

    update_term_meta( $original_term_id, wpr_translate_get_translated_term_meta_key( $language ), $translated_term_id );
    update_term_meta( $translated_term_id, 'wpr_original_term_id', $original_term_id );

    return true;
}

/**
 * Resolve translated term IDs from the translation table as a fallback.
 *
 * @param int    $term_id  Source term ID.
 * @param string $taxonomy Taxonomy slug.
 * @param string $language Target language code.
 * @param bool   $strict   True to disable source-language fallback.
 *
 * @return array{id:int,direct:bool}
 */
function wpr_translate_lookup_translated_term_id_from_trid( $term_id, $taxonomy, $language, $strict = false ) {
    $term_id  = absint( $term_id );
    $taxonomy = sanitize_key( $taxonomy );
    $language = wpr_translate_normalize_term_translation_language( $language );
    $strict   = (bool) $strict;
    $result   = array(
        'id'     => 0,
        'direct' => false,
    );

    if ( $term_id <= 0 || '' === $taxonomy || '' === $language ) {
        return $result;
    }

    $group = wpr_translation_get_translation_group( $term_id, wpr_translation_get_taxonomy_element_type( $taxonomy ) );

    if ( empty( $group['trid'] ) || empty( $group['translations'] ) || ! is_array( $group['translations'] ) ) {
        return $result;
    }

    $translations = $group['translations'];

    if ( ! empty( $translations[ $language ] ) ) {
        $candidate = absint( $translations[ $language ] );

        if ( $candidate > 0 && wpr_translate_is_valid_term_for_taxonomy( $candidate, $taxonomy ) ) {
            $result['id']     = $candidate;
            $result['direct'] = true;
            return $result;
        }
    }

    if ( $strict ) {
        return $result;
    }

    $source_language = isset( $group['source_language'] ) ? wpr_translate_normalize_term_translation_language( $group['source_language'] ) : '';

    if ( '' !== $source_language && ! empty( $translations[ $source_language ] ) ) {
        $candidate = absint( $translations[ $source_language ] );

        if ( $candidate > 0 && wpr_translate_is_valid_term_for_taxonomy( $candidate, $taxonomy ) ) {
            $result['id'] = $candidate;
            return $result;
        }
    }

    return $result;
}

/**
 * Shared resolver for strict and non-strict term ID lookups.
 *
 * @param int    $term_id  Source term ID.
 * @param string $taxonomy Taxonomy slug.
 * @param string $language Target language code.
 * @param bool   $strict   True to return blank when translation is missing.
 *
 * @return int|string
 */
function wpr_translate_lookup_translated_term_id_internal( $term_id, $taxonomy, $language, $strict = false ) {
    // Cache translations to avoid repeated lookups within the request.
    static $cache = array();

    $term_id  = absint( $term_id );
    $taxonomy = sanitize_key( $taxonomy );
    $language = wpr_translate_normalize_term_translation_language( $language );
    $strict   = (bool) $strict;

    if ( $term_id <= 0 || '' === $taxonomy || '' === $language ) {
        return $strict ? '' : 0;
    }

    $cache_key = $term_id . ':' . $taxonomy . ':' . $language . ':' . ( $strict ? '1' : '0' );

    if ( isset( $cache[ $cache_key ] ) ) {
        return $cache[ $cache_key ];
    }

    $canonical_term_id = wpr_translate_resolve_original_term_id( $term_id, $taxonomy );

    if ( $canonical_term_id <= 0 ) {
        $canonical_term_id = $term_id;
    }

    $meta_term_id = wpr_translate_read_translated_term_id_from_meta( $canonical_term_id, $taxonomy, $language );

    if ( $meta_term_id > 0 ) {
        $cache[ $cache_key ] = $meta_term_id;
        return $cache[ $cache_key ];
    }

    $trid_lookup = wpr_translate_lookup_translated_term_id_from_trid( $canonical_term_id, $taxonomy, $language, $strict );

    if ( $trid_lookup['id'] > 0 ) {
        if ( $trid_lookup['direct'] && $trid_lookup['id'] !== $canonical_term_id ) {
            // Backfill term meta when TRID resolves a direct target-language mapping.
            wpr_translate_link_term_translation_meta( $canonical_term_id, $trid_lookup['id'], $language, $taxonomy );
        }

        $cache[ $cache_key ] = $trid_lookup['id'];
        return $cache[ $cache_key ];
    }

    if ( $strict ) {
        $cache[ $cache_key ] = '';
        return $cache[ $cache_key ];
    }

    $cache[ $cache_key ] = $canonical_term_id;
    return $cache[ $cache_key ];
}

/**
 * Resolve the best term ID for the requested language.
 *
 * @param int    $term_id  Source term ID being translated.
 * @param string $taxonomy Taxonomy slug the term belongs to.
 * @param string $language Language code that should be preferred.
 *
 * @return int Term ID that should be used for filtering.
 */
function wpr_translate_lookup_translated_term_id( $term_id, $taxonomy, $language ) {
    return (int) wpr_translate_lookup_translated_term_id_internal( $term_id, $taxonomy, $language, false );
}

/**
 * Resolve the translated term ID but return blank when no localized term exists.
 *
 * @param int    $term_id  Source term ID being translated.
 * @param string $taxonomy Taxonomy slug the term belongs to.
 * @param string $language Language code that should be preferred.
 *
 * @return int|string Translated term ID or an empty string when unavailable.
 */
function wpr_translate_lookup_translated_term_id_strict( $term_id, $taxonomy, $language ) {
    return wpr_translate_lookup_translated_term_id_internal( $term_id, $taxonomy, $language, true );
}

/**
 * Resolve translated term IDs by slug using meta-first mapping with TRID fallback.
 *
 * @param string $slug     Source term slug.
 * @param string $taxonomy Taxonomy slug.
 * @param string $language Target language code.
 * @param bool   $strict   True to return blank when translation is missing.
 *
 * @return int|string
 */
function wpr_translate_lookup_translated_term_id_by_slug( $slug, $taxonomy, $language, $strict = false ) {
    $slug     = sanitize_title( (string) $slug );
    $taxonomy = sanitize_key( $taxonomy );
    $strict   = (bool) $strict;

    if ( '' === $slug || '' === $taxonomy || ! taxonomy_exists( $taxonomy ) ) {
        return $strict ? '' : 0;
    }

    $term = get_term_by( 'slug', $slug, $taxonomy );

    if ( ! $term instanceof WP_Term ) {
        return $strict ? '' : 0;
    }

    if ( $strict ) {
        return wpr_translate_lookup_translated_term_id_strict( (int) $term->term_id, $taxonomy, $language );
    }

    return wpr_translate_lookup_translated_term_id( (int) $term->term_id, $taxonomy, $language );
}

/**
 * Resolve translated term IDs by name using meta-first mapping with TRID fallback.
 *
 * @param string $name     Source term name.
 * @param string $taxonomy Taxonomy slug.
 * @param string $language Target language code.
 * @param bool   $strict   True to return blank when translation is missing.
 *
 * @return int|string
 */
function wpr_translate_lookup_translated_term_id_by_name( $name, $taxonomy, $language, $strict = false ) {
    $name     = sanitize_text_field( (string) $name );
    $taxonomy = sanitize_key( $taxonomy );
    $strict   = (bool) $strict;

    if ( '' === $name || '' === $taxonomy || ! taxonomy_exists( $taxonomy ) ) {
        return $strict ? '' : 0;
    }

    $terms = get_terms(
        array(
            'taxonomy'   => $taxonomy,
            'hide_empty' => false,
            'name'       => $name,
        )
    );

    if ( is_wp_error( $terms ) || empty( $terms ) ) {
        return $strict ? '' : 0;
    }

    usort(
        $terms,
        static function ( $left, $right ) {
            return absint( $left->term_id ) <=> absint( $right->term_id );
        }
    );

    foreach ( $terms as $term ) {
        if ( ! $term instanceof WP_Term ) {
            continue;
        }

        $resolved = $strict
            ? wpr_translate_lookup_translated_term_id_strict( (int) $term->term_id, $taxonomy, $language )
            : wpr_translate_lookup_translated_term_id( (int) $term->term_id, $taxonomy, $language );

        if ( is_numeric( $resolved ) && absint( $resolved ) > 0 ) {
            return absint( $resolved );
        }
    }

    return $strict ? '' : 0;
}
