<?php
/**
 * Language router bootstrap and helpers.
 *
 * This file wires up the translation-aware routing logic that inspects incoming
 * requests, matches them to active languages, and updates WordPress globals so
 * the remainder of the request lifecycle runs in the correct locale. The router
 * is also responsible for generating language switch URLs and coordinating with
 * the canonical redirect logic to avoid redirect loops when translations are
 * missing.
 */

if ( ! defined( 'ABSPATH' ) ) {
    // Do not allow direct execution outside of WordPress.
    exit;
}

// Load language management helpers required by the router.
require_once __DIR__ . '/language-manager.php';

// Cache request-scoped data that multiple router helpers consume.
$GLOBALS['wpr_translate_language_router'] = $GLOBALS['wpr_translate_language_router'] ?? array(
    'language_from_url'              => false,
    'matched_language'               => null,
    'original_path'                  => '',
    'base_segments'                  => array(),
    'relative_segments'              => array(),
    'relative_path_without_language' => '',
    'query_string'                   => '',
);

/**
 * Write debug information to the error log with a consistent prefix.
 *
 * @param string $message Message to write to the log.
 * @param array  $context Optional array of additional context data that will be JSON-encoded.
 *
 * @return void
 */
function wpr_translate_language_router_log( $message, $context = array() ) {
    // Logging disabled.
    return;
}

/**
 * Register the actions and filters required for the language router to operate.
 *
 * @return void
 */
function wpr_translate_language_router_bootstrap() {
    // Log initialization to aid debugging.
    wpr_translate_language_router_log( 'Bootstrapping router hooks.' );

    // Prepare the request path before WordPress builds the main query.
    add_action( 'setup_theme', 'wpr_translate_language_router_prepare_request_path', 0 );
    add_action( 'init', 'wpr_translate_language_router_prepare_request_path', 0 );

    // Register rewrite rules for language-prefixed URLs once permalink structures are available.
    add_action( 'init', 'wpr_translate_language_router_register_rewrite_rules', 20 );

    // Expose custom query variables used to store language information.
    add_filter( 'query_vars', 'wpr_translate_language_router_register_query_vars' );

    // Detect the visitor's language as soon as the request is parsed.
    add_action( 'parse_request', 'wpr_translate_language_router_detect_language', 0 );

    // Prevent canonical redirects from fighting language-prefixed front-page URLs.
    add_filter( 'redirect_canonical', 'wpr_translate_maybe_skip_front_page_canonical_redirect', 10, 2 );
    // Avoid redirect loops caused by toggling language prefixes.
    add_filter( 'redirect_canonical', 'wpr_translate_maybe_skip_language_prefix_redirect', 2, 2 );

    // Allow the front-end language switcher to store the preferred language cookie via AJAX.
    add_action( 'wp_ajax_wpr_translate_set_language_cookie', 'wpr_translate_handle_language_cookie_ajax' );
    add_action( 'wp_ajax_nopriv_wpr_translate_set_language_cookie', 'wpr_translate_handle_language_cookie_ajax' );
}

/**
 * Prevent canonical redirects that only add/remove a language prefix.
 *
 * @param string|false $redirect_url  Canonical redirect destination.
 * @param string       $requested_url Requested URL before redirection.
 *
 * @return string|false
 */
function wpr_translate_maybe_skip_language_prefix_redirect( $redirect_url, $requested_url ) {
    if ( false === $redirect_url ) {
        return $redirect_url;
    }

    if ( ! function_exists( 'wpr_translate_get_active_languages' ) ) {
        return $redirect_url;
    }

    $requested_path = wp_parse_url( (string) $requested_url, PHP_URL_PATH );
    $redirect_path  = wp_parse_url( (string) $redirect_url, PHP_URL_PATH );

    if ( ! is_string( $requested_path ) || ! is_string( $redirect_path ) ) {
        return $redirect_url;
    }

    $requested_path = '/' . ltrim( $requested_path, '/' );
    $redirect_path  = '/' . ltrim( $redirect_path, '/' );

    $languages = wpr_translate_get_active_languages();

    foreach ( $languages as $language ) {
        $slug = function_exists( 'wpr_translate_get_language_slug' ) ? wpr_translate_get_language_slug( $language ) : '';

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

        $slug = '/' . trim( $slug, '/' ) . '/';

        // Redirect adds a language slug.
        if ( 0 === strpos( $redirect_path . '/', $slug ) && 0 !== strpos( $requested_path . '/', $slug ) ) {
            $candidate = '/' . ltrim( substr( $redirect_path . '/', strlen( $slug ) ), '/' );

            if ( rtrim( $candidate, '/' ) === rtrim( $requested_path, '/' ) ) {
                return false;
            }
        }

        // Redirect removes a language slug.
        if ( 0 === strpos( $requested_path . '/', $slug ) && 0 !== strpos( $redirect_path . '/', $slug ) ) {
            $candidate = '/' . ltrim( substr( $requested_path . '/', strlen( $slug ) ), '/' );

            if ( rtrim( $candidate, '/' ) === rtrim( $redirect_path, '/' ) ) {
                return false;
            }
        }
    }

    return $redirect_url;
}

/**
 * Store the preferred language cookie when the front-end switcher triggers an AJAX request.
 *
 * @return void
 */
function wpr_translate_handle_language_cookie_ajax() {
    // Verify the nonce to prevent unsolicited cookie updates.
    if ( ! check_ajax_referer( 'wpr_translate_language_cookie', 'nonce', false ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'Invalid request.', 'wpr-translate' ),
            ),
            403
        );
    }

    // Sanitize the requested language code.
    $code = isset( $_POST['language'] ) ? sanitize_key( wp_unslash( $_POST['language'] ) ) : '';

    if ( '' === $code ) {
        wp_send_json_error(
            array(
                'message' => __( 'Missing language code.', 'wpr-translate' ),
            ),
            400
        );
    }

    if ( ! function_exists( 'wpr_translate_get_language' ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'Language lookup is unavailable.', 'wpr-translate' ),
            ),
            500
        );
    }

    $language = wpr_translate_get_language( $code );

    if ( null === $language || empty( $language['is_active'] ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'Unknown language.', 'wpr-translate' ),
            ),
            404
        );
    }

    if ( function_exists( 'wpr_translate_language_router_store_language_cookie' ) ) {
        // Persist the preference using the same cookie helper as the router.
        wpr_translate_language_router_store_language_cookie( $language['code'] );
    }

    if ( function_exists( 'wpr_translate_debug_log' ) ) {
        $current_post_id = isset( $_POST['current_post_id'] ) ? absint( wp_unslash( $_POST['current_post_id'] ) ) : 0;
        $translation_id  = 0;

        if ( $current_post_id && function_exists( 'wpr_translation_get_translation_group' ) && function_exists( 'wpr_translation_get_element_type' ) ) {
            $post_type = get_post_type( $current_post_id );

            if ( $post_type ) {
                $element_type = wpr_translation_get_element_type( $post_type );
                $group        = wpr_translation_get_translation_group( $current_post_id, $element_type );

                if ( ! empty( $group['translations'] ) && is_array( $group['translations'] ) ) {
                    $target_code = sanitize_key( $language['code'] );

                    if ( isset( $group['translations'][ $target_code ] ) ) {
                        $translation_id = absint( $group['translations'][ $target_code ] );
                    }
                }
            }
        }

        wpr_translate_debug_log(
            'Language change requested.',
            array(
                'requested_code' => sanitize_key( $language['code'] ),
                'current_post_id'=> $current_post_id,
                'translation_id' => $translation_id,
            )
        );
    }

    wp_send_json_success(
        array(
            'language' => $language['code'],
        )
    );
}

/**
 * Determine whether the router should skip preparing the current request path.
 *
 * @return bool
 */
function wpr_translate_language_router_should_skip_prepare() {
    // Skip path preparation in admin, AJAX, or CLI contexts.
    $skip = wp_doing_ajax() || is_admin() || 'cli' === php_sapi_name();

    if ( ! $skip && wpr_translate_language_router_is_rest_request() ) {
        // Do not alter REST API request paths.
        $skip = true;
    }

    /**
     * Filter whether the router should skip preparing the current request path.
     *
     * Allow integrations (such as automated tests) to override the default
     * behaviour that bails out for CLI, admin, or AJAX contexts.
     */
    return (bool) apply_filters( 'wpr_translate_language_router_skip_prepare', $skip );
}

/**
 * Determine whether the language detection step should run for the current request.
 *
 * @return bool
 */
function wpr_translate_language_router_should_skip_detection() {
    // Skip detection in admin and AJAX flows by default.
    $skip = is_admin() || wp_doing_ajax();

    if ( ! $skip && wpr_translate_language_router_is_rest_request() ) {
        // REST requests should not be assigned front-end language state.
        $skip = true;
    }

    /**
     * Filter whether the router should skip detecting the current request's language.
     *
     * This mirrors {@see wpr_translate_language_router_should_skip_prepare()} but allows
     * integrations to force detection during CLI-driven requests without impacting
     * production behaviour.
     */
    return (bool) apply_filters( 'wpr_translate_language_router_skip_detection', $skip );
}

/**
 * Detect whether the current request targets the WordPress REST API.
 *
 * @return bool
 */
function wpr_translate_language_router_is_rest_request() {
    // Use native WordPress helpers when available.
    if ( function_exists( 'wp_doing_rest' ) && wp_doing_rest() ) {
        return true;
    }

    if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
        return true;
    }

    // Check for the rest_route query arg.
    $rest_route = isset( $_GET['rest_route'] ) ? sanitize_text_field( wp_unslash( $_GET['rest_route'] ) ) : '';
    if ( '' !== $rest_route ) {
        return true;
    }

    // Fallback to inspecting the request URI.
    $uri = isset( $_SERVER['REQUEST_URI'] ) ? (string) wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
    if ( '' !== $uri && false !== strpos( $uri, '/wp-json/' ) ) {
        return true;
    }

    return false;
}

/**
 * Normalise the incoming request path and detect language-prefixed segments.
 *
 * The function stores breakdowns of the URL so other helpers can rebuild
 * language-aware links later in the request lifecycle.
 *
 * @return void
 */
function wpr_translate_language_router_prepare_request_path() {
    // Track whether the request path has already been prepared.
    static $prepared = false;

    // Allow tests or integrations to force re-preparing the request path.
    if ( apply_filters( 'wpr_translate_language_router_force_prepare', false ) ) {
        $prepared = false;
    }

    if ( $prepared ) {
        // Avoid recalculating the request path when the function runs on multiple early hooks.
        return;
    }

    $prepared = true;
    global $wpr_translate_language_router;

    // Only manipulate front-end requests. Admin, AJAX, and CLI contexts remain untouched.
    if ( wpr_translate_language_router_should_skip_prepare() ) {
        wpr_translate_language_router_log( 'Skipping request path preparation in non-frontend context.' );
        return;
    }

    // Capture the raw request URI and split it into path/query components.
    $uri        = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '/';
    $parts      = wp_parse_url( $uri );
    $home_path  = wpr_translate_language_router_get_site_base_path();
    $path       = isset( $parts['path'] ) ? $parts['path'] : '/';
    $query      = isset( $parts['query'] ) ? $parts['query'] : '';
    $path       = '' === $path ? '/' : $path;

    // Store base request information for downstream helpers.
    $wpr_translate_language_router['original_path']                 = $path;
    $wpr_translate_language_router['query_string']                  = $query;
    $wpr_translate_language_router['base_segments']                 = array_values( array_filter( explode( '/', $home_path ) ) );
    $wpr_translate_language_router['relative_segments']             = array_values( array_filter( explode( '/', trim( $path, '/' ) ) ) );
    $wpr_translate_language_router['relative_path_without_language']= '';
    $wpr_translate_language_router['language_from_url']             = false;

    // Remove the site's base path segments from the request-specific portion.
    if ( ! empty( $wpr_translate_language_router['base_segments'] ) ) {
        foreach ( $wpr_translate_language_router['base_segments'] as $index => $segment ) {
            if ( isset( $wpr_translate_language_router['relative_segments'][ $index ] ) && $wpr_translate_language_router['relative_segments'][ $index ] === $segment ) {
                unset( $wpr_translate_language_router['relative_segments'][ $index ] );
            }
        }
        $wpr_translate_language_router['relative_segments'] = array_values( $wpr_translate_language_router['relative_segments'] );
    }

    // When the request points to the site root, no language analysis is necessary.
    if ( empty( $wpr_translate_language_router['relative_segments'] ) ) {
        $wpr_translate_language_router['relative_path_without_language'] = wpr_translate_language_router_build_relative_path( array() );
        wpr_translate_language_router_log(
            'No relative segments detected; using root path.',
            array( 'original_path' => $path )
        );
        if ( function_exists( 'wpr_translate_set_current_language' ) && function_exists( 'wpr_translate_get_default_language' ) ) {
            $default_language = wpr_translate_get_default_language();

            if ( is_array( $default_language ) ) {
                wpr_translate_set_current_language(
                    $default_language,
                    array( 'reload_theme_translations' => true )
                );
            }
        }
        return;
    }

    // Retrieve the list of languages to compare against the leading path segment.
    $languages = wpr_translate_get_active_languages();
    if ( empty( $languages ) ) {
        $wpr_translate_language_router['relative_path_without_language'] = wpr_translate_language_router_build_relative_path( $wpr_translate_language_router['relative_segments'] );
        wpr_translate_language_router_log(
            'No active languages configured; keeping original path.',
            array( 'relative_path' => $wpr_translate_language_router['relative_path_without_language'] )
        );

        return;
    }

    // Compare the first path segment to known language slugs.
    $first_segment = strtolower( $wpr_translate_language_router['relative_segments'][0] );
    foreach ( $languages as $language ) {
        $slug = wpr_translate_get_language_slug( $language );
        if ( '' === $slug ) {
            // Skip languages that do not have a configured slug.
            continue;
        }
        if ( strtolower( $slug ) === $first_segment || strtolower( $language['code'] ) === $first_segment ) {
            // Store the matched language and remove the prefix from the path.
            $wpr_translate_language_router['matched_language']              = $language;
            $wpr_translate_language_router['language_from_url']             = true;
            $wpr_translate_language_router['relative_segments']             = array_values( array_slice( $wpr_translate_language_router['relative_segments'], 1 ) );
            // Remove base path segments again in case the language prefix masked them (e.g. index.php or subdirectory installs).
            if ( ! empty( $wpr_translate_language_router['base_segments'] ) ) {
                foreach ( $wpr_translate_language_router['base_segments'] as $index => $segment ) {
                    if ( isset( $wpr_translate_language_router['relative_segments'][ $index ] ) && $wpr_translate_language_router['relative_segments'][ $index ] === $segment ) {
                        unset( $wpr_translate_language_router['relative_segments'][ $index ] );
                    }
                }
                $wpr_translate_language_router['relative_segments'] = array_values( $wpr_translate_language_router['relative_segments'] );
            }
            $wpr_translate_language_router['relative_path_without_language'] = wpr_translate_language_router_build_relative_path( $wpr_translate_language_router['relative_segments'] );

            wpr_translate_language_router_log(
                'Matched language from URL segment.',
                array(
                    'segment'       => $first_segment,
                    'language_code' => isset( $language['code'] ) ? $language['code'] : '',
                )
            );
            wpr_translate_set_current_language(
                $language,
                array( 'reload_theme_translations' => true )
            );
            // Update the request URI so WordPress parses the correct path.
            wpr_translate_language_router_update_server_request_uri( wpr_translate_language_router_build_full_path(), $query );

            wpr_translate_language_router_log(
                'Updated request URI after matching language.',
                array( 'new_path' => $wpr_translate_language_router['relative_path_without_language'] )
            );

            return;
        }
    }

    // No language match was found, so retain the detected relative path.
    $wpr_translate_language_router['relative_path_without_language'] = wpr_translate_language_router_build_relative_path( $wpr_translate_language_router['relative_segments'] );
    wpr_translate_language_router_log(
        'No language matched from URL; preserving relative path.',
        array( 'relative_path' => $wpr_translate_language_router['relative_path_without_language'] )
    );

    if ( function_exists( 'wpr_translate_set_current_language' ) && function_exists( 'wpr_translate_get_default_language' ) ) {
        $resolved_language = $wpr_translate_language_router['matched_language'];

        if ( ! is_array( $resolved_language ) ) {
            $resolved_language = wpr_translate_get_default_language();
        }

        if ( is_array( $resolved_language ) ) {
            wpr_translate_set_current_language(
                $resolved_language,
                array( 'reload_theme_translations' => true )
            );
        }
    }

}

/**
 * Determine the active language for the current request.
 *
 * @param WP $wp WordPress request object containing parsed query vars.
 *
 * @return void
 */
function wpr_translate_language_router_detect_language( $wp ) {
    global $wpr_translate_language_router;

    // Do not interfere with admin or AJAX flows because they handle localisation separately.
    if ( wpr_translate_language_router_should_skip_detection() ) {
        wpr_translate_language_router_log( 'Skipping language detection for admin or AJAX request.' );
        return;
    }
    // Start with any language matched during path preparation.
    $language = $wpr_translate_language_router['matched_language'];
    if ( null === $language && isset( $wp->query_vars['wpr_lang'] ) ) {
        // Fallback to the rewrite-provided language code when no direct URL match was found.
        $language = wpr_translate_get_language( $wp->query_vars['wpr_lang'] );
        $wpr_translate_language_router['language_from_url'] = true;
        if ( isset( $wp->query_vars['wpr_lang_path'] ) ) {
            // Reconstruct the path portion that followed the language slug.
            $path = trim( (string) $wp->query_vars['wpr_lang_path'], '/' );
            $wpr_translate_language_router['relative_segments']             = array_values( array_filter( explode( '/', $path ) ) );
            $wpr_translate_language_router['relative_path_without_language'] = wpr_translate_language_router_build_relative_path( $wpr_translate_language_router['relative_segments'] );
            // Keep PHP request globals in sync with the rewritten path.
            wpr_translate_language_router_update_server_request_uri( wpr_translate_language_router_build_full_path(), $wpr_translate_language_router['query_string'] );
            wpr_translate_language_router_log(
                'Parsed language from query vars.',
                array(
                    'language_code' => isset( $language['code'] ) ? $language['code'] : '',
                    'path'          => $wpr_translate_language_router['relative_path_without_language'],
                )
            );
        }
    }
    if ( null === $language && ! $wpr_translate_language_router['language_from_url'] ) {
        // Default to the site language when no language information was supplied.
        $language = wpr_translate_get_default_language();
        wpr_translate_language_router_log(
            'Falling back to default language.',
            array( 'language_code' => null !== $language && isset( $language['code'] ) ? $language['code'] : '' )
        );
    }

    if ( null === $language ) {
        // Resolve a language preference (for example, from cookies) as a final fallback.
        $language = wpr_translate_resolve_preferred_language();
        wpr_translate_language_router_log(
            'Resolving preferred language as final fallback.',
            array( 'language_code' => null !== $language && isset( $language['code'] ) ? $language['code'] : '' )
        );
    }
    if ( null === $language ) {
        wpr_translate_language_router_log( 'No language could be resolved; aborting detection.' );
        return;
    }
    // Apply the resolved language for the remainder of the request.
    wpr_translate_set_current_language(
        $language,
        array( 'reload_theme_translations' => true )
    );
    wpr_translate_language_router_log(
        'Set current language during detection.',
        array( 'language_code' => isset( $language['code'] ) ? $language['code'] : '' )
    );
    if ( ! defined( 'WPR_TRANSLATE_CURRENT_LANGUAGE' ) ) {
        // Provide a global constant for templates that rely on it.
        define( 'WPR_TRANSLATE_CURRENT_LANGUAGE', $language['code'] );
    }
    // Persist language preference only when the language was explicitly
    // determined from the URL.  Fallback-resolved languages (e.g. when
    // the browser requests a .js.map file with no language prefix) must
    // not overwrite the visitor's existing cookie preference.
    if ( $wpr_translate_language_router['language_from_url'] ) {
        wpr_translate_language_router_store_language_cookie( $language['code'] );
    }
    unset( $wp->query_vars['wpr_lang'], $wp->query_vars['wpr_lang_path'] );
}

/**
 * Register custom query variables consumed by the router.
 *
 * @param array $vars Existing public query variables.
 *
 * @return array
 */
function wpr_translate_language_router_register_query_vars( $vars ) {
    wpr_translate_language_router_log( 'Registering router query vars.' );
    // Append language query variables for rewrite parsing.
    $vars[] = 'wpr_lang';
    $vars[] = 'wpr_lang_path';
    return $vars;
}

/**
 * Add rewrite rules that capture language-prefixed URL structures.
 *
 * @return void
 */
function wpr_translate_language_router_register_rewrite_rules() {
    // Register rewrite rules only when languages are active.
    $languages = wpr_translate_get_active_languages();
    if ( empty( $languages ) ) {
        wpr_translate_language_router_log( 'Skipping rewrite rule registration; no active languages.' );
        return;
    }
    // Ensure WordPress recognises the custom query variables in rewrite patterns.
    add_rewrite_tag( '%wpr_lang%', '([^/]+)' );
    add_rewrite_tag( '%wpr_lang_path%', '(.*)' );
    foreach ( $languages as $language ) {
        $slug = wpr_translate_get_language_slug( $language );
        if ( '' === $slug ) {
            wpr_translate_language_router_log(
                'Skipping rewrite rule for language without slug.',
                array( 'language_code' => isset( $language['code'] ) ? $language['code'] : '' )
            );
            continue;
        }
        // Match the language slug optionally followed by a trailing path.
        $pattern = '^' . preg_quote( $slug, '/' ) . '(?:/(.*))?/?$';
        add_rewrite_rule( $pattern, 'index.php?wpr_lang=' . $slug . '&wpr_lang_path=$matches[1]', 'top' );
        wpr_translate_language_router_log(
            'Registered rewrite rule for language.',
            array(
                'language_code' => isset( $language['code'] ) ? $language['code'] : '',
                'slug'          => $slug,
            )
        );
    }
}

/**
 * Retrieve the current request path without any language prefix.
 *
 * @return string
 */
function wpr_translate_language_router_get_path_without_language() {
    global $wpr_translate_language_router;
    // Default to '/' when no relative path has been computed.
    return '' === $wpr_translate_language_router['relative_path_without_language'] ? '/' : $wpr_translate_language_router['relative_path_without_language'];
}

/**
 * Fetch the cached query string captured during request preparation.
 *
 * @return string
 */
function wpr_translate_language_router_get_query_string() {
    global $wpr_translate_language_router;
    // Use the cached query string captured during request preparation.
    return $wpr_translate_language_router['query_string'];
}

/**
 * Build a URL that switches to the provided language while preserving the path.
 *
 * @param array $language Language data array returned by the language manager.
 *
 * @return string
 */
function wpr_translate_language_router_get_switch_url( $language ) {
    // Build the language-specific prefix when needed.
    $slug = wpr_translate_get_language_slug( $language );
    $path = wpr_translate_language_router_get_path_without_language();
    $path = '/' === $path ? '' : trim( $path, '/' );
    if ( '' === $slug ) {
        // Languages without a slug reuse the unprefixed path.
        $relative = $path;
    } else {
        $relative = untrailingslashit( $slug );
        if ( '' !== $path ) {
            // Append the original path segments after the language slug.
            $relative .= '/' . $path;
        }
    }
    $relative = '' === $relative ? '/' : '/' . trim( $relative, '/' ) . '/';
    $url      = wpr_translate_language_router_build_home_url( $relative );
    // Respect the site's trailing-slash preference for the generated URL.
    $url      = user_trailingslashit( $url );
    $query    = wpr_translate_language_router_get_query_string();
    if ( '' !== $query ) {
        // Preserve existing query parameters when generating language switch links.
        $separator = false === strpos( $url, '?' ) ? '?' : '&';
        $url      .= $separator . $query;
    }
    wpr_translate_language_router_log(
        'Generated switch URL for language.',
        array(
            'language_code' => isset( $language['code'] ) ? $language['code'] : '',
            'url'           => $url,
        )
    );
    return $url;
}

/**
 * Generate an absolute URL for the provided relative path.
 *
 * @param string $relative_path Relative path (including leading slash) to append to the home option.
 *
 * @return string
 */
function wpr_translate_language_router_build_home_url( $relative_path ) {
    // Start with the configured home URL.
    $home = get_option( 'home' );
    if ( ! is_string( $home ) || '' === $home ) {
        // Fallback to siteurl when the home option is missing.
        $home = get_option( 'siteurl' );
    }
    if ( ! is_string( $home ) || '' === $home ) {
        // As a final fallback, defer to WordPress' computed home URL.
        $home = home_url( '/' );
    }
    $home          = trailingslashit( set_url_scheme( $home ) );
    $relative_path = ltrim( $relative_path, '/' );
    if ( '' === $relative_path ) {
        return $home;
    }
    return $home . $relative_path;
}

/**
 * Retrieve the base path portion of the site's home URL.
 *
 * @return string
 */
function wpr_translate_language_router_get_site_base_path() {
    // Resolve the URL that represents the site's home.
    $home = get_option( 'home' );
    if ( ! is_string( $home ) || '' === $home ) {
        $home = get_option( 'siteurl' );
    }
    if ( ! is_string( $home ) || '' === $home ) {
        $home = home_url( '/' );
    }
    $home_url = wp_parse_url( set_url_scheme( $home ) );
    if ( false === $home_url ) {
        // Invalid home URL structure; treat as having no base path.
        return '';
    }
    return isset( $home_url['path'] ) ? trim( $home_url['path'], '/' ) : '';
}

/**
 * Assemble a relative path from an array of path segments.
 *
 * @param array $segments Path segments without slashes.
 *
 * @return string
 */
function wpr_translate_language_router_build_relative_path( $segments ) {
    if ( empty( $segments ) ) {
        // Use the root path when no segments exist.
        return '/';
    }
    // Encode each segment to produce a safe URL path.
    return '/' . implode( '/', array_map( 'rawurlencode', $segments ) ) . '/';
}

/**
 * Combine base and relative segments into a full request path.
 *
 * @return string
 */
function wpr_translate_language_router_build_full_path() {
    global $wpr_translate_language_router;
    // Merge base and request-specific segments.
    $segments = array_merge( $wpr_translate_language_router['base_segments'], $wpr_translate_language_router['relative_segments'] );
    if ( empty( $segments ) ) {
        return '/';
    }
    // Encode each segment to ensure consistency with URL encoding rules.
    return '/' . implode( '/', array_map( 'rawurlencode', $segments ) ) . '/';
}

/**
 * Update PHP superglobals so WordPress interprets the rewritten request path.
 *
 * @param string $path  New request path, including leading slash.
 * @param string $query Query string to append to the updated request URI.
 *
 * @return void
 */
function wpr_translate_language_router_update_server_request_uri( $path, $query ) {
    // Default to root path when an empty path is provided.
    $path = '' === $path ? '/' : $path;
    $uri  = $path;
    if ( '' !== $query ) {
        // Rebuild the request URI with any original query string parameters intact.
        $uri .= '?' . $query;
    }
    $_SERVER['REQUEST_URI'] = $uri;
    if ( isset( $_SERVER['PATH_INFO'] ) ) {
        // Ensure PATH_INFO reflects the modified path for compatibility with certain setups.
        $_SERVER['PATH_INFO'] = $path;
    }
    wpr_translate_language_router_log(
        'Updated server request URI.',
        array(
            'path'  => $path,
            'query' => $query,
        )
    );
}

/**
 * Store the visitor's language preference so future requests can reuse it.
 *
 * @param string $code Language code to persist in cookies.
 *
 * @return void
 */
function wpr_translate_language_router_store_language_cookie( $code ) {
    if ( headers_sent() ) {
        wpr_translate_language_router_log( 'Could not store language cookie because headers already sent.' );
        return;
    }
    // Prepare expiration before setting the cookie.
    // Persist the preference for one month to balance freshness and user expectations.
    $expiration = time() + MONTH_IN_SECONDS;
    setcookie( 'wpestate_translation_lang_pref', $code, $expiration, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, is_ssl(), true );
    if ( COOKIEPATH !== SITECOOKIEPATH ) {
        // Mirror the cookie to SITECOOKIEPATH when it differs from COOKIEPATH to cover all contexts.
        setcookie( 'wpestate_translation_lang_pref', $code, $expiration, SITECOOKIEPATH ? SITECOOKIEPATH : '/', COOKIE_DOMAIN, is_ssl(), true );
    }
    // Update the PHP superglobal so the preference is available immediately in this request.
    $_COOKIE['wpestate_translation_lang_pref'] = $code;
    wpr_translate_language_router_log(
        'Stored language preference cookie.',
        array( 'language_code' => $code )
    );
}

/**
 * Prevent canonical redirects from collapsing language-prefixed front-page URLs
 * when the requested language falls back to the default page content.
 *
 * This keeps URLs like `/es/` accessible even if the translated front page is
 * missing, avoiding a redirect loop between `/` and the language-prefixed path.
 *
 * @param string|false $redirect_url  Canonical redirect destination.
 * @param string       $requested_url Original requested URL.
 *
 * @return string|false Either the original redirect target or false to skip redirecting.
 */
function wpr_translate_maybe_skip_front_page_canonical_redirect( $redirect_url, $requested_url ) {
    unset( $requested_url );

    // Let WordPress handle non-front-end contexts through its default logic.
    if ( is_admin() || is_feed() || is_trackback() ) {
        return $redirect_url;
    }

    // Only intervene when the canonical redirect targets the front page.
    if ( ! function_exists( 'is_front_page' ) || ! is_front_page() ) {
        return $redirect_url;
    }

    $front_page_id = (int) get_option( 'page_on_front' );

    // Abort if a static front page is not configured.
    if ( $front_page_id <= 0 ) {
        return $redirect_url;
    }

    $queried_id = get_queried_object_id();

    // Respect canonical redirects for other pages that might share the template.
    if ( $queried_id && (int) $queried_id !== $front_page_id ) {
        return $redirect_url;
    }

    if ( ! function_exists( 'wpr_translate_get_current_language' ) || ! function_exists( 'wpr_translate_get_default_language' ) ) {
        return $redirect_url;
    }

    $active_language  = wpr_translate_get_current_language();
    $default_language = wpr_translate_get_default_language();

    $active_code  = isset( $active_language['code'] ) ? (string) $active_language['code'] : '';
    $default_code = isset( $default_language['code'] ) ? (string) $default_language['code'] : '';

    // Skip the override when either language cannot be determined.
    if ( '' === $active_code || '' === $default_code ) {
        return $redirect_url;
    }

    // Allow canonical redirects for the default language.
    if ( strtolower( $active_code ) === strtolower( $default_code ) ) {
        return $redirect_url;
    }

    if ( ! function_exists( 'wpr_translation_get_element_type' ) || ! function_exists( 'wpr_translation_get_translation_group' ) ) {
        return $redirect_url;
    }

    $element_type = wpr_translation_get_element_type( 'page' );
    $group        = wpr_translation_get_translation_group( $front_page_id, $element_type );

    $normalized_active_code = strtolower( sanitize_key( $active_code ) );
    $translations           = array();

    // Normalise translation group data to a simple map for lookup.
    if ( ! empty( $group['translations'] ) && is_array( $group['translations'] ) ) {
        foreach ( $group['translations'] as $language_code => $translated_post_id ) {
            $translations[ strtolower( sanitize_key( $language_code ) ) ] = (int) $translated_post_id;
        }
    }

    if ( isset( $translations[ $normalized_active_code ] ) ) {
        $translated_id = (int) $translations[ $normalized_active_code ];

        // Honour the redirect when a distinct translation exists for the active language.
        if ( $translated_id > 0 && $translated_id !== $front_page_id ) {
            return $redirect_url;
        }
    }

    // No translated front page exists, so prevent the redirect loop by skipping it.
    return false;
}

