<?php
/**
 * Runtime translation helpers for plugin-provided text domains.
 *
 * @package WPR_Translate
 */

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

/**
 * Determine the locale that should be used for runtime translations.
 *
 * @return string
 */
function wpr_translate_get_active_locale() {
    if ( is_admin() && ! wp_doing_ajax() ) {
        // Use the WordPress/admin locale for wp-admin screens.
        if ( function_exists( 'determine_locale' ) ) {
            return determine_locale();
        }

        return get_locale();
    }

    if ( function_exists( 'wpr_translate_get_current_language' ) ) {
        // Prefer the currently resolved language.
        $language = wpr_translate_get_current_language();

        if ( is_array( $language ) && ! empty( $language['locale'] ) ) {
            // Return the language locale when available.
            return (string) $language['locale'];
        }
    }

    if ( function_exists( 'wpr_translate_get_context_language' ) ) {
        // Fall back to the context language when no current language exists.
        $language = wpr_translate_get_context_language();

        if ( is_array( $language ) && ! empty( $language['locale'] ) ) {
            // Return the context locale.
            return (string) $language['locale'];
        }
    }

    if ( function_exists( 'wpr_translate_get_default_language' ) ) {
        // Use the default language as the final plugin-level fallback.
        $language = wpr_translate_get_default_language();

        if ( is_array( $language ) && ! empty( $language['locale'] ) ) {
            // Return the default locale.
            return (string) $language['locale'];
        }
    }

    if ( function_exists( 'determine_locale' ) ) {
        // Use WordPress locale detection when available.
        return determine_locale();
    }

    // Fall back to the site locale.
    return get_locale();
}

/**
 * Determine the active language code for runtime translations.
 *
 * @return string
 */
function wpr_translate_get_active_language_code() {
    if ( function_exists( 'wpr_translate_get_current_language' ) ) {
        // Prefer the current language code.
        $language = wpr_translate_get_current_language();

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

    if ( function_exists( 'wpr_translate_get_context_language' ) ) {
        // Fall back to the context language code.
        $language = wpr_translate_get_context_language();

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

    if ( function_exists( 'wpr_translate_get_default_language' ) ) {
        // Fall back to the default language code.
        $language = wpr_translate_get_default_language();

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

    // As a last resort, match the locale against stored languages.
    $languages = get_option( 'wpr_translate_languages', array() );
    $locale    = strtolower( str_replace( '-', '_', wpr_translate_get_active_locale() ) );

    foreach ( $languages as $language ) {
        if ( empty( $language['code'] ) ) {
            // Skip invalid entries.
            continue;
        }

        $language_locale = isset( $language['locale'] ) ? strtolower( str_replace( '-', '_', (string) $language['locale'] ) ) : '';

        if ( '' !== $language_locale && $language_locale === $locale ) {
            return (string) $language['code'];
        }
    }

    return '';
}

/**
 * Determine if a custom MO file has been loaded for a domain and locale.
 *
 * The information is sourced from the global registry populated by
 * {@see wpr_translate_record_loaded_mofile()}. This guard prevents duplicate
 * loads and helps other runtime features decide whether to defer to gettext.
 *
 * @param string $domain Text domain.
 * @param string $locale Locale identifier.
 *
 * @return bool True when a plugin-generated MO file is active.
 */
function wpr_translate_has_custom_mofile_loaded( $domain, $locale = '' ) {
    // Normalize inputs before checking the registry.
    $domain = wpr_translate_normalize_translation_domain( $domain );

    if ( '' === $domain ) {
        return false;
    }

    if ( '' === $locale ) {
        // Default to the active locale when none is provided.
        $locale = wpr_translate_get_active_locale();
    }

    $locale = wpr_translate_normalize_translation_locale( $locale );

    if ( '' === $locale ) {
        return false;
    }

    // The registry is only initialised once a MO file loading attempt occurs.
    if ( ! isset( $GLOBALS['wpr_translate_loaded_mofiles'] ) || ! is_array( $GLOBALS['wpr_translate_loaded_mofiles'] ) ) {
        // No registry indicates no custom MO files were loaded.
        return false;
    }

    $key = strtolower( $domain ) . '|' . strtolower( $locale );

    if ( ! isset( $GLOBALS['wpr_translate_loaded_mofiles'][ $key ] ) ) {
        return false;
    }

    // Inspect the recorded source to confirm the custom file was loaded.
    $source = isset( $GLOBALS['wpr_translate_loaded_mofiles'][ $key ]['source'] )
        ? (string) $GLOBALS['wpr_translate_loaded_mofiles'][ $key ]['source']
        : '';

    // Only mark as loaded when the stored source confirms the plugin file.
    return ( 'custom' === $source );
}

/**
 * Determine potential translation contexts for a given domain.
 *
 * @param string $domain Text domain.
 *
 * @return array
 */
function wpr_translate_get_candidate_contexts_for_domain( $domain ) {
    // Build candidate contexts based on domain variations.
    $candidates = array();
    $normalized = wpr_translate_normalize_translation_domain( $domain );

    $variants = array();

    if ( '' !== $normalized ) {
        $variants[] = $normalized;
    }

    if ( function_exists( 'sanitize_title' ) ) {
        // Add a sanitized variant for compatibility with stored contexts.
        $sanitized = sanitize_title( $domain );

        if ( '' !== $sanitized ) {
            $variants[] = $sanitized;
        }
    }

    $variants = array_unique( array_filter( $variants ) );

    foreach ( $variants as $variant ) {
        // Add theme and plugin prefixes for scanning.
        $candidates[] = 'theme:' . $variant;
        $candidates[] = 'plugin:' . $variant;
        $candidates[] = $variant;
    }

    return array_values( array_unique( array_filter( $candidates ) ) );
}

/**
 * Determine whether a text domain has runtime translations for a language.
 *
 * @param string $domain        Text domain.
 * @param string $language_code Active language code.
 *
 * @return bool
 */
function wpr_translate_domain_has_runtime_translations( $domain, $language_code ) {
    global $wpdb;

    // Cache results per domain/language to reduce database hits.
    static $cache = array();

    $domain        = wpr_translate_normalize_translation_domain( $domain );
    $language_code = (string) $language_code;

    if ( '' === $domain || '' === $language_code ) {
        return false;
    }

    $cache_key = strtolower( $domain ) . '|' . strtolower( $language_code );

    if ( array_key_exists( $cache_key, $cache ) ) {
        return (bool) $cache[ $cache_key ];
    }

    // Query the strings table for any matching translations.
    $table    = $wpdb->prefix . 'wpestate_translation_strings';
    $contexts = wpr_translate_get_candidate_contexts_for_domain( $domain );
    $found    = false;

    if ( ! empty( $contexts ) ) {
        $placeholders = implode( ',', array_fill( 0, count( $contexts ), '%s' ) );
        $params       = array_merge( $contexts, array( $language_code ) );

        $sql      = "SELECT 1 FROM {$table} WHERE context IN ({$placeholders}) AND language_code = %s LIMIT 1";
        $prepared = $wpdb->prepare( $sql, $params );

        if ( $prepared ) {
            // Execute the prepared query.
            $found = (bool) $wpdb->get_var( $prepared );
        }
    }

    if ( ! $found ) {
        // Fall back to a LIKE query for legacy context values.
        $like = '%' . $wpdb->esc_like( $domain ) . '%';

        $found = (bool) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT 1 FROM {$table} WHERE context LIKE %s AND language_code = %s LIMIT 1",
                $like,
                $language_code
            )
        );
    }

    // Store the result in cache for this request.
    $cache[ $cache_key ] = $found;

    return $found;
}

/**
 * Look up a stored translation for the provided domain and language.
 *
 * @param string $domain        Text domain.
 * @param string $original      Original string value.
 * @param string $language_code Active language code.
 *
 * @return string
 */
function wpr_translate_lookup_runtime_translation( $domain, $original, $language_code ) {
    global $wpdb;

    // Cache string lookups by domain, value hash, and language.
    static $cache = array();

    $domain        = wpr_translate_normalize_translation_domain( $domain );
    $original      = (string) $original;
    $language_code = (string) $language_code;

    if ( '' === $domain || '' === $original || '' === $language_code ) {
        return '';
    }

    $cache_key = strtolower( $domain ) . '|' . md5( $original ) . '|' . strtolower( $language_code );

    if ( array_key_exists( $cache_key, $cache ) ) {
        // Return cached value when available.
        return $cache[ $cache_key ];
    }

    $name  = 'str_' . md5( $original );
    $table = $wpdb->prefix . 'wpestate_translation_strings';

    $contexts = wpr_translate_get_candidate_contexts_for_domain( $domain );
    $value    = '';

    if ( ! empty( $contexts ) ) {
        // Build a query that matches known contexts.
        $placeholders = implode( ',', array_fill( 0, count( $contexts ), '%s' ) );
        $params       = array_merge( $contexts, array( $name, $language_code ) );

        $sql      = "SELECT translation FROM {$table} WHERE context IN ({$placeholders}) AND name = %s AND language_code = %s LIMIT 1";
        $prepared = $wpdb->prepare( $sql, $params );

        if ( $prepared ) {
            // Execute the prepared query.
            $value = $wpdb->get_var( $prepared );
        }
    }

    if ( '' === $value || null === $value ) {
        // Fall back to a LIKE query if no context match was found.
        $like = '%' . $wpdb->esc_like( $domain ) . '%';

        $value = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT translation FROM {$table} WHERE context LIKE %s AND name = %s AND language_code = %s LIMIT 1",
                $like,
                $name,
                $language_code
            )
        );
    }

    if ( null === $value ) {
        // Cache empty result and return.
        $cache[ $cache_key ] = '';

        return '';
    }

    $value = (string) $value;

    if ( '' === trim( wp_strip_all_tags( $value ) ) ) {
        // Do not return empty or whitespace-only translations.
        $cache[ $cache_key ] = '';

        return '';
    }

    // Cache the resolved translation.
    $cache[ $cache_key ] = $value;

    return $cache[ $cache_key ];
}

/**
 * Determine whether runtime database translations should be used.
 *
 * Runtime overrides are only required while editing strings within the
 * administrative interface. In all other contexts gettext-backed strings
 * should remain untouched to avoid unexpected fallbacks.
 *
 * @return bool
 */
function wpr_translate_runtime_should_use_database_translations() {
    if ( ! is_admin() ) {
        // Database overrides apply only in the admin string editor.
        return false;
    }

    $page = '';

    if ( isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        // Read the page slug from the query string.
        $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    } elseif ( isset( $_POST['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
        // Read the page slug from POST as a fallback.
        $page = sanitize_text_field( wp_unslash( $_POST['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
    }

    // Only enable overrides on the strings editor page.
    return ( 'wpr-translate-strings' === $page );
}

/**
 * Provide runtime translations for text domains when necessary.
 *
 * @param string $current  Current translation value.
 * @param string $original Original string passed to gettext.
 * @param string $domain   Text domain.
 *
 * @return string
 */
function wpr_translate_maybe_provide_translation( $current, $original, $domain ) {
    // Normalize inputs before checking database translations.
    $domain   = wpr_translate_normalize_translation_domain( $domain );
    $original = (string) $original;

    if ( '' === $domain || '' === $original ) {
        return $current;
    }

    if ( '' !== $current && $current !== $original ) {
        // Preserve existing translated strings.
        return $current;
    }

    if ( ! wpr_translate_runtime_should_use_database_translations() ) {
        // Only run when the admin string editor is active.
        return $current;
    }

    $locale = wpr_translate_get_active_locale();

    if ( wpr_translate_has_custom_mofile_loaded( $domain, $locale ) ) {
        // Respect MO file translations when they already exist.
        return $current;
    }

    $language_code = wpr_translate_get_active_language_code();

    if ( '' === $language_code ) {
        // No language code available means no lookup can occur.
        return $current;
    }

    if ( ! wpr_translate_domain_has_runtime_translations( $domain, $language_code ) ) {
        // Skip when the domain has no runtime translations.
        return $current;
    }

    // Look up the translated value in the database.
    $replacement = wpr_translate_lookup_runtime_translation( $domain, $original, $language_code );
    $replacement = is_string( $replacement ) ? $replacement : '';
    $replacement = apply_filters( 'wpr_translate_runtime_translation', $replacement, $original, $domain, $language_code, '' );

    if ( '' === $replacement ) {
        // Keep original when no translation is found.
        return $current;
    }

    return $replacement;
}
