<?php
if (!defined('ABSPATH')) {
  exit;
}

class AWXCT_Language_Routing
{
  protected static $allowed_langs = [];
  // Merkt sich die Sprache der aktuellen Anfrage
  protected static $current_lang = '';


/** @var string */
protected static $cache_group = 'awxct';

/**
 * Debug logger (only when WP_DEBUG).
 *
 * @param string $message
 * @return void
 */
protected static function debug_log($message)
{
  if (defined('WP_DEBUG') && WP_DEBUG) {
    do_action('awxct_debug_log', (string) $message);
  }
}

/**
 * Validate table identifier (alnum + underscore only).
 *
 * @param string $table
 * @return string Empty string if invalid.
 */
protected static function validate_table_ident($table): string
{
  $table = (string) $table;
  if (preg_match('/^[A-Za-z0-9_]+$/', $table)) {
    return $table;
  }
  return '';
}

/**
 * Return validated + escaped table identifier for SQL backticks context.
 *
 * @param string $table
 * @return string Empty string if invalid.
 */
protected static function table_sql($table): string
{
  $valid = self::validate_table_ident($table);
  if ($valid === '') {
    return '';
  }
  return esc_sql($valid);
}

/**
 * Get sanitized REQUEST_URI (no query parsing, just sanitization).
 *
 * @return string
 */
protected static function get_request_uri(): string
{
  return isset($_SERVER['REQUEST_URI'])
    ? sanitize_text_field(wp_unslash((string) $_SERVER['REQUEST_URI']))
    : '';
}

/**
 * Get sanitized cookie value for awxct_lang (lowercase, dash-separated).
 *
 * @return string
 */
protected static function get_cookie_lang(): string
{
  $val = '';

  if ( isset( $_COOKIE['awxct_lang'] ) ) {
    $val = sanitize_key( wp_unslash( $_COOKIE['awxct_lang'] ) );
  }

  if ( $val === '' ) {
    return '';
  }

  // Normalisieren: lowercase, dash-separated.
  $val = strtolower( str_replace( '_', '-', trim( (string) $val ) ) );

  return $val;
}

  public static function init()
  {
    // Sprachen laden (Whitelist)
    add_action('init', array(__CLASS__, 'load_allowed_langs'), 5);

    // Rewrite + Query-Var
    add_action('init', array(__CLASS__, 'add_rewrite'), 9);
    add_filter('query_vars', array(__CLASS__, 'add_query_var'));

    // Canonical nur für Sprachpfade unterdrücken
    add_filter('redirect_canonical', array(__CLASS__, 'maybe_disable_canonical'), 10, 2);

    // Mapping von URL → Übersetzungsseite
    add_filter('request', array(__CLASS__, 'map_request_to_translation'), 5);

    // Links im aktiven Sprachkontext ausgeben
    add_filter('post_type_link', array(__CLASS__, 'filter_post_type_link'), 10, 2);
    add_filter('page_link', array(__CLASS__, 'filter_page_link'), 10, 2);
    add_filter('post_link', array(__CLASS__, 'filter_post_link'), 10, 2);
    add_filter('wp_nav_menu_objects', array(__CLASS__, 'filter_menu_objects'), 10, 2);

    // Cookie (merken, welche Sprache gewählt wurde)
    add_action('template_redirect', array(__CLASS__, 'remember_lang_cookie'), 0);

    // Kein Auto-Redirect – Platzhalter (keine Schleifen).
    add_action('template_redirect', array(__CLASS__, 'maybe_redirect_to_active_lang'), 20);
  }

  /*
   * ============================================================
   * 1) Aktive Sprachen aus DB lesen (bzw. Helper nutzen)
   * ============================================================
   */
  public static function load_allowed_langs()
  {
    // Neue Helper-Variante: wenn vorhanden, immer bevorzugen
    if (function_exists('awxct_available_locales')) {
      $locs = awxct_available_locales();  // z.B. ["de-DE", "en-US"]
      $norm = [];

      foreach ($locs as $lc) {
        // Für URLs/Regex weiterhin klein mit Bindestrich: "de-de"
        $norm[] = strtolower(str_replace('_', '-', trim((string) $lc)));
      }

      self::$allowed_langs = array_values(array_unique(array_filter($norm)));
    } else {
      // Fallback: alte Logik, falls Helper nicht existieren
      global $wpdb;

      $tbl_lang = $wpdb->prefix . 'awxct_languages';
      $tbl_lang_ident = self::validate_table_ident($tbl_lang);
      $tbl_lang_sql = self::table_sql($tbl_lang);

      if ($tbl_lang_ident !== '' && $tbl_lang_sql !== '') {
        $cache_key_exists = 'awxct_tbl_lang_exists';
        $exists = wp_cache_get($cache_key_exists, self::$cache_group);

        if (false === $exists) {
          // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- cached below
          $exists = (string) $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $tbl_lang_ident));
          wp_cache_set($cache_key_exists, $exists, self::$cache_group, 300);
        }

        if ((string) $exists === (string) $tbl_lang_ident) {
          $cache_key_codes = 'awxct_lang_codes';
          $codes = wp_cache_get($cache_key_codes, self::$cache_group);

          if (false === $codes) {
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table identifier validated (regex) + esc_sql(); cached below
            $codes = (array) $wpdb->get_col("SELECT code FROM `{$tbl_lang_sql}` WHERE active = 1");
            wp_cache_set($cache_key_codes, $codes, self::$cache_group, 300);
          }

          if (!empty($codes)) {
            $norm = [];
            foreach ($codes as $c) {
              $norm[] = strtolower(trim((string) $c));
            }
            self::$allowed_langs = array_values(array_unique(array_filter($norm)));
          }
        }
      }
    }

    // Bei Änderung der Sprachliste: Rewrite-Regeln flushen
    $hash = md5(implode('|', self::$allowed_langs));
    $opt = 'awxct_routing_langs_hash';
    $prev = get_option($opt);
    if ($prev !== $hash) {
      if (is_admin()) {
        flush_rewrite_rules(false);
      }
      update_option($opt, $hash, true);
    }
  }

  /**
   * Getter für erlaubte Sprachen – z.B. für JS (dynamic, keine Hardcodes).
   */
  public static function get_allowed_langs(): array
  {
    return is_array(self::$allowed_langs) ? self::$allowed_langs : [];
  }

  /*
   * ============================================================
   * 2) Rewrite-Regeln für /{lang}/ und /{lang}/{path}
   * ============================================================
   */
  public static function add_rewrite()
  {
    if (empty(self::$allowed_langs)) {
      // Fallback: generisches Pattern
      $lang_regex = '([A-Za-z-]{2,7})';
    } else {
      $escaped = [];
      foreach (self::$allowed_langs as $c) {
        $escaped[] = preg_quote($c, '/');
      }
      $lang_regex = '(' . implode('|', $escaped) . ')';
    }

    add_rewrite_tag('%awxct_lang%', $lang_regex);

    // /{lang}/
    add_rewrite_rule(
      '^' . $lang_regex . '/?$',
      'index.php?awxct_lang=$matches[1]',
      'top'
    );

    // /{lang}/{path}
    // $matches[1] = lang, $matches[2] = path
    add_rewrite_rule(
      '^' . $lang_regex . '/(.+)$',
      'index.php?awxct_lang=$matches[1]&pagename=$matches[2]',
      'top'
    );
  }

  /*
   * ============================================================
   * 3) Query-Variablen registrieren
   * ============================================================
   */
  public static function add_query_var($vars)
  {
    $vars[] = 'awxct_lang';
    $vars[] = 'awxct_set_lang';  // explizit setzen ohne Prefix
    return $vars;
  }

  /*
   * ============================================================
   * 4) Canonical-Redirects nur für Sprachpfade deaktivieren
   * ============================================================
   */

  public static function maybe_disable_canonical($redirect, $requested)
  {
    // Im Admin nichts anfassen
    if (is_admin()) {
      return $redirect;
    }

    if (!is_string($requested) || $requested === '') {
      return $redirect;
    }

    $path = wp_parse_url($requested, PHP_URL_PATH);
    if (!is_string($path)) {
      $path = '';
    }

    $home = home_url('/');
    $home_path = wp_parse_url($home, PHP_URL_PATH);
    if (!is_string($home_path)) {
      $home_path = '';
    }

    $rel = $path;
    if ($home_path && $home_path !== '/' && strpos($rel, $home_path) === 0) {
      $rel = substr($rel, strlen($home_path));
    }
    $rel = '/' . ltrim($rel, '/');
    if ($rel === '//') {
      $rel = '/';
    }

    $trimmed = trim($rel, '/');
    $first = $trimmed === '' ? '' : explode('/', $trimmed)[0];

    $allowed = self::$allowed_langs;
    $allowed_lower = is_array($allowed) ? array_map('strtolower', $allowed) : [];

    // Log alles schön aus
    self::debug_log('[AWX CANONICAL] requested=' . $requested
      . ' path=' . $path
      . ' home_path=' . $home_path
      . ' rel=' . $rel
      . ' first=' . $first
      . ' allowed=' . wp_json_encode( $allowed_lower )
      . ' redirect_in=' . wp_json_encode($redirect));

    if ($first && in_array(strtolower($first), $allowed_lower, true)) {
      self::debug_log('[AWX CANONICAL] DISABLE canonical for lang-prefix "' . $first . '"');
      return false;
    }

    self::debug_log('[AWX CANONICAL] keep canonical (no lang-prefix)');
    return $redirect;
  }

  /*
   * ============================================================
   * 5) Request-Mapping: URL → Übersetzte Seite
   * ============================================================
   */
  public static function map_request_to_translation($vars)
  {
    // Grobes Debug
    self::debug_log('[AWX Routing] user=' . (is_user_logged_in() ? 'in' : 'out')
      . ' uri=' . (self::get_request_uri())
      . ' awxct_lang=' . ($vars['awxct_lang'] ?? ''));

    
    // Elementor/REST (wp-json) niemals anfassen (Editor hängt sonst beim Laden)
    $uri = self::get_request_uri();
    if ($uri !== '' && strpos($uri, '/wp-json/') !== false) { return $vars; }
    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only check for Elementor preview; no state change.
    if (! empty( sanitize_text_field( wp_unslash( (string) ( $_GET['elementor-preview'] ?? '' ) ) ) )) { return $vars; }

    // Fallback: Sprache aus REQUEST_URI ziehen, falls awxct_lang leer ist
    // Nur dann, wenn die URL direkt mit /{lang}/ beginnt.
    if (empty($vars['awxct_lang'])) {
      $req_uri = self::get_request_uri();
      $path = $req_uri !== '' ? (wp_parse_url($req_uri, PHP_URL_PATH) ?: '') : '';
      if (preg_match('#^/([a-z]{2}-[a-z]{2})(?:/|$)#i', $path, $m)) {
        $vars['awxct_lang'] = strtolower($m[1]);
        self::debug_log('[AWX Routing Fallback] path=' . $path . ' → awxct_lang=' . $vars['awxct_lang']);
      }
    }

    $lang = isset($vars['awxct_lang']) ? strtolower($vars['awxct_lang']) : '';
    if (!$lang) {
      self::$current_lang = '';
      return $vars;
    }

    // Sprache für diese Anfrage merken
    self::$current_lang = $lang;

    if (!class_exists('AWXCT_Frontend')) {
      return $vars;
    }

    /*
     * ---------------- Slug-Bereinigung ----------------
     * - Wenn slug == lang (de-de) → Startseite
     * - Wenn slug mit lang/ beginnt (de-de/about-us) → Prefix entfernen
     */
    $slug = '';
    if (!empty($vars['pagename'])) {
      $slug = $vars['pagename'];
    } elseif (!empty($vars['name'])) {
      $slug = $vars['name'];
    }

    if ($slug !== '') {
      $slug_trim = trim($slug, '/');
      $slug_norm = strtolower($slug_trim);

      // Fall 1: slug == lang → wie /de-de/ behandeln
      if ($slug_norm === $lang) {
        self::debug_log('[AWX Routing] slug "' . $slug . '" entspricht lang="' . $lang . '" → als Startseite behandeln');
        unset($vars['pagename'], $vars['name'], $vars['page']);
      } else {
        // Fall 2: slug beginnt mit lang/, z.B. de-de/about-us
        $lang_prefix = $lang . '/';
        if (strpos($slug_norm, $lang_prefix) === 0) {
          $rest = substr($slug_trim, strlen($lang_prefix));  // z.B. about-us oder foo/bar
          self::debug_log('[AWX Routing] slug "' . $slug . '" beginnt mit lang="' . $lang . '" → normalisiert zu "' . $rest . '"');
          $vars['pagename'] = $rest;
          unset($vars['name']);
        }
      }
    }

    /*
     * A) /{lang}/ → front page (translation, sonst fallback)
     */
    $has_path = !empty($vars['pagename']) || !empty($vars['name']) || !empty($vars['page']);
    if (!$has_path) {
      $front_id = (int) get_option('page_on_front');
      self::debug_log('[AWX Routing A] lang=' . $lang . ' front_id=' . $front_id);

      if ($front_id > 0) {
        $original_id = AWXCT_Frontend::get_original_id($front_id);
        if (!$original_id) {
          $original_id = $front_id;
        }

        self::debug_log('[AWX Routing A] original_id=' . $original_id);

        // Übersetzung direkt aus Mapping-Tabelle holen
        $target_id = self::resolve_translation_post_id($original_id, $lang);

        $final_id = ($target_id > 0) ? $target_id : $front_id;
        self::debug_log('[AWX Routing A] chosen page_id=' . $final_id . ' (target_id=' . $target_id . ' / fallback=' . $front_id . ')');

        $vars['page_id'] = $final_id;
        unset($vars['pagename'], $vars['name'], $vars['page'], $vars['post_type'], $vars['attachment']);
      }
      return $vars;
    }

    /*
     * B) /{lang}/{path}/ → resolve page/post/CPT and map if available
     */

    $path = !empty($vars['pagename']) ? $vars['pagename'] : (!empty($vars['name']) ? $vars['name'] : '');
    if ($path === '') {
      return $vars;
    }

    $path_norm = strtolower(trim($path, '/'));

    $def = self::get_default_lang_safe();  // z.B. "en-us"
    $is_home_like = false;

    if ($path_norm === 'home') {
      $is_home_like = true;
    } elseif ($path_norm === 'home-' . $lang) {
      $is_home_like = true;
    } elseif ($path_norm === 'home-' . $def) {
      $is_home_like = true;
    }

    if ($is_home_like) {
      self::debug_log(
        '[AWX Routing B] treat slug "' . $path_norm
        . '" as FRONT-PAGE alias for lang="' . $lang . '"'
      );

      $front_id = (int) get_option('page_on_front');
      if ($front_id > 0) {
        $original_id = AWXCT_Frontend::get_original_id($front_id);
        if (!$original_id) {
          $original_id = $front_id;
        }

        self::debug_log('[AWX Routing B] alias → original_id=' . $original_id);

        $target_id = self::resolve_translation_post_id($original_id, $lang);
        $final_id = ($target_id > 0) ? $target_id : $front_id;

        self::debug_log(
          '[AWX Routing B] alias → chosen page_id=' . $final_id
          . ' (target_id=' . $target_id . ' / fallback=' . $front_id . ')'
        );

        $vars['page_id'] = $final_id;
        unset($vars['pagename'], $vars['name'], $vars['page'], $vars['post_type'], $vars['attachment']);
      }

      return $vars;
    }

    $public_types = array_values(get_post_types(array('public' => true), 'names'));
    $maybe = get_page_by_path($path, OBJECT, $public_types);
    if (!$maybe) {
      self::debug_log('[AWX Routing B] get_page_by_path miss for path=' . $path);
      return $vars;
    }

    if ( self::is_clone_post( $maybe->ID ) ) {
      self::debug_log('[AWX Routing B] path=' . $path . ' is a CLONE (no translation mapping) -> keep requested post');
      return $vars;
    }

    $post_lang = self::get_post_lang_code($maybe->ID);
    self::debug_log('[AWX Routing B] lang=' . $lang . ' path=' . $path . ' original_post_lang=' . $post_lang);

    $original_id = AWXCT_Frontend::get_original_id($maybe->ID);
    if (!$original_id) {
      $original_id = $maybe->ID;
    }

    self::debug_log('[AWX Routing B] lang=' . $lang . ' path=' . $path . ' original_id=' . $original_id);

    $target_id = self::resolve_translation_post_id($original_id, $lang);
    if ($target_id <= 0) {
      self::debug_log('[AWX Routing B] no active translation found -> keep original');
      return $vars;
    }

    $target = get_post($target_id);
    if (!$target) {
      self::debug_log('[AWX Routing B] get_post miss for target_id=' . $target_id);
      return $vars;
    }

    if ($target->post_type === 'page') {
      $vars['page_id'] = (int) $target->ID;
      unset($vars['pagename'], $vars['name']);
    } else {
      $vars['p'] = (int) $target->ID;
      $vars['post_type'] = $target->post_type;
      unset($vars['name'], $vars['pagename']);
    }
    unset($vars['attachment']);

    self::debug_log('[AWX Routing B] mapped to post_id=' . $target->ID . ' type=' . $target->post_type);

    return $vars;
  }

  /*
   * ============================================================
   * 6) Hilfen für Sprach-Prefix in URLs
   * ============================================================
   */

  protected static function get_default_lang_safe()
  {
    $def = 'de';
    if (class_exists('AWXCT_Frontend') && method_exists('AWXCT_Frontend', 'get_default_lang')) {
      $def = strtolower(AWXCT_Frontend::get_default_lang());
    }
    $def = strtolower(str_replace('_', '-', $def));
    return $def ?: 'de';
  }

  protected static function prefix_url($url, $lang)
  {
    $def = self::get_default_lang_safe();

    if (empty($lang)) {
      return $url;
    }

    $home = get_option('home');
    $home_trail = trailingslashit($home);
    $home_parts = wp_parse_url($home_trail);
    $home_path = isset($home_parts['path']) ? $home_parts['path'] : '/';

    $parts = wp_parse_url($url);
    if (!$parts || empty($parts['path'])) {
      return $url;
    }

    if (!empty($parts['host']) && !empty($home_parts['host']) && strcasecmp($parts['host'], $home_parts['host']) !== 0) {
      return $url;
    }

    $path = $parts['path'];

    if ($home_path && $home_path !== '/' && stripos($path, $home_path) === 0) {
      $rel = substr($path, strlen($home_path));
    } else {
      $rel = ltrim($path, '/');
    }

    $rel = ltrim($rel, '/');
    if (preg_match('#^([a-z]{2}-[a-z]{2})(/.*)?$#i', $rel, $m)) {
      $rel = isset($m[2]) ? ltrim($m[2], '/') : '';
    }

    $new_path = rtrim($home_path, '/');

    if ($lang !== $def) {
      $new_path .= '/' . $lang;
    }

    if ($rel !== '') {
      $new_path .= '/' . $rel;
    }

    if ($new_path === '') {
      $new_path = '/';
    }

    $new_path = '/' . ltrim($new_path, '/');
    if (substr($path, -1) === '/') {
      $new_path = trailingslashit($new_path);
    }

    $parts['path'] = $new_path;

    $scheme = isset($parts['scheme']) ? $parts['scheme'] . '://' : (isset($home_parts['scheme']) ? $home_parts['scheme'] . '://' : '');
    $host = $parts['host'] ?? ($home_parts['host'] ?? '');
    $port = isset($parts['port']) ? ':' . $parts['port'] : '';
    $query = isset($parts['query']) ? '?' . $parts['query'] : '';
    $frag = isset($parts['fragment']) ? '#' . $parts['fragment'] : '';

    return $scheme . $host . $port . $parts['path'] . $query . $frag;
  }

  protected static function get_current_lang_code_for_urls()
  {
    $allowed = self::$allowed_langs;
    $normalize = function ($code) {
      $code = (string) $code;
      if ($code === '') {
        return '';
      }
      $code = str_replace('_', '-', trim($code));
      return strtolower($code);
    };

    if (!empty(self::$current_lang)) {
      $lang = $normalize(self::$current_lang);
      if ($lang && (empty($allowed) || in_array($lang, $allowed, true))) {
        return $lang;
      }
    }

    $lang = get_query_var('awxct_lang');
    if (!empty($lang)) {
      $lang = $normalize($lang);
      if ($lang && (empty($allowed) || in_array($lang, $allowed, true))) {
        return $lang;
      }
    }

    $awxct_set_lang = isset( $_GET['awxct_set_lang'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['awxct_set_lang'] ) ) : '';
    $nonce        = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['_wpnonce'] ) ) : '';
    if ( '' !== $awxct_set_lang && $nonce && wp_verify_nonce( $nonce, 'awxct_set_lang' ) ) {
      $lang = $normalize( $awxct_set_lang );
      if ( $lang && ( empty( $allowed ) || in_array( $lang, $allowed, true ) ) ) {
        return $lang;
      }
    }

    $uri = self::get_request_uri();
    if ($uri !== '') {
      $path = wp_parse_url($uri, PHP_URL_PATH) ?: '';

      $home = home_url('/');
      $homePath = wp_parse_url($home, PHP_URL_PATH);
      if (!is_string($homePath)) {
        $homePath = '/';
      }

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

      if ($path !== '' && preg_match('~^/([a-z]{2}(?:-[a-z]{2})?)(?:/|$)~i', $path, $m)) {
        $code = $normalize($m[1]);
        if ($code && (empty($allowed) || in_array($code, $allowed, true))) {
          return $code;
        }
      }
    }

    // 3) IMPORTANT: Do NOT use cookie to build URLs.
    // The language switcher may store a cookie for convenience, but it must
    // never influence routing or link-generation. URLs must be deterministic
    // and only depend on the current request path (/{lang}/...) or explicit
    // switcher action. Otherwise pages in default language can "jump" back
    // to a previously selected non-default language.

    $def = self::get_default_lang_safe();
    if ($def) {
      $def = $normalize($def);
      if ($def && (empty($allowed) || in_array($def, $allowed, true))) {
        return $def;
      }
    }

    return '';
  }

  public static function filter_post_type_link($permalink, $post)
  {
    if (is_admin() || (function_exists('wp_doing_ajax') && wp_doing_ajax()) || (defined('REST_REQUEST') && REST_REQUEST)) {
      return $permalink;
    }

    $lang = self::get_current_lang_code_for_urls();
    if (empty($lang)) {
      return $permalink;
    }
    return self::prefix_url($permalink, $lang);
  }

  public static function filter_page_link($permalink, $post_id)
  {
    if (is_admin() || (function_exists('wp_doing_ajax') && wp_doing_ajax()) || (defined('REST_REQUEST') && REST_REQUEST)) {
      return $permalink;
    }

    $lang = self::get_current_lang_code_for_urls();
    if (empty($lang)) {
      return $permalink;
    }
    return self::prefix_url($permalink, $lang);
  }

  public static function filter_post_link($permalink, $post)
  {
    if (is_admin() || (function_exists('wp_doing_ajax') && wp_doing_ajax()) || (defined('REST_REQUEST') && REST_REQUEST)) {
      return $permalink;
    }

    $lang = self::get_current_lang_code_for_urls();
    if (empty($lang)) {
      return $permalink;
    }
    return self::prefix_url($permalink, $lang);
  }

  public static function filter_menu_objects($items, $args)
  {
    if (is_admin() ||
        (function_exists('wp_doing_ajax') && wp_doing_ajax()) ||
        (defined('REST_REQUEST') && REST_REQUEST)) {
      return $items;
    }

    $lang = self::get_current_lang_code_for_urls();

    if (empty($lang)) {
      return $items;
    }

    $home = get_option('home');
    $home_parts = wp_parse_url($home);
    $home_host = $home_parts['host'] ?? '';

    foreach ($items as $item) {
      if (empty($item->url)) {
        continue;
      }

      $old = $item->url;
      $parts = wp_parse_url($item->url);

      if (!empty($parts['host']) &&
          !empty($home_host) &&
          strcasecmp($parts['host'], $home_host) !== 0) {
        continue;
      }

      $new_url = $old;

      if (!empty($item->object_id) &&
          class_exists('AWXCT_Frontend')) {
        $src_id = (int) $item->object_id;
        if ( self::is_clone_post( $src_id ) ) {
          $new_url  = self::prefix_url( $old, $lang );
          $item->url = $new_url;
          continue;
        }
        $original_id = AWXCT_Frontend::get_original_id($src_id);
        if (!$original_id) {
          $original_id = $src_id;
        }

        $front_id = (int) get_option('page_on_front');
        $front_root = $front_id ? (int) AWXCT_Frontend::get_original_id($front_id) : 0;
        if (!$front_root && $front_id) {
          $front_root = $front_id;
        }

        $target_id = self::resolve_translation_post_id($original_id, $lang);

        if ($front_root && $original_id === $front_root) {
          $def = self::get_default_lang_safe();

          if ($lang === $def) {
            $new_url = trailingslashit(home_url('/'));
          } else {
            $new_url = trailingslashit(home_url('/' . $lang . '/'));
          }
        } else {
          if ($target_id > 0 && $target_id !== $src_id) {
            // For nav menus we prefer the original (root) permalink and only
            // add the language prefix. This keeps menu URLs clean like
            // /de-de/join-us/ even if the translation page's real slug is
            // something like join-us-de-de or a fully translated slug.
            $base = get_permalink($original_id);
            if ($base) {
              $new_url = self::prefix_url($base, $lang);
            }
          } else {
            $new_url = self::prefix_url($old, $lang);
          }
        }
      } else {
        $new_url = self::prefix_url($old, $lang);
      }

      $item->url = $new_url;
    }

    return $items;
  }

  public static function remember_lang_cookie()
  {
    if (is_admin() || is_feed() || defined('REST_REQUEST')) {
      return;
    }

    $lang = get_query_var('awxct_lang');

    $lang = is_string( $lang ) ? sanitize_text_field( $lang ) : '';
    $lang = strtolower( str_replace( '_', '-', trim( (string) $lang ) ) );

    if ( empty( $lang ) ) {
      if ( isset( $_GET['awxct_set_lang'], $_GET['_wpnonce'] ) ) {
        $nonce = sanitize_text_field( wp_unslash( (string) $_GET['_wpnonce'] ) );
        if ( $nonce && wp_verify_nonce( $nonce, 'awxct_set_lang' ) ) {
          $param = sanitize_text_field( wp_unslash( (string) $_GET['awxct_set_lang'] ) );
          if ( $param ) {
            $lang = strtolower( str_replace( '_', '-', trim( (string) $param ) ) );
          }
        }
      }
    }

    $def = self::get_default_lang_safe();

    // If there is no language ...
    // This ensures premium dict/templating cannot "stick" to a previous non-default cookie.
    if ( empty( $lang ) ) {
      $uri = self::get_request_uri();
      $home = home_url( '/' );
      $homePath = wp_parse_url( $home, PHP_URL_PATH );
      if ( ! is_string( $homePath ) ) {
        $homePath = '/';
      }
      $path = wp_parse_url( $uri, PHP_URL_PATH );
      if ( is_string( $path ) ) {
        $rel = $path;
        if ( $homePath && $homePath !== '/' && strpos( $rel, $homePath ) === 0 ) {
          $rel = substr( $rel, strlen( $homePath ) );
        }
        $rel = '/' . ltrim( $rel, '/' );
        if ( ! preg_match( '#^/([a-z]{2}-[a-z]{2})(?:/|$)#i', $rel ) ) {
          // No language prefix => default context => delete cookie if present
          if ( self::get_cookie_lang() !== '' ) {
            self::clear_lang_cookie();
          }
        }
      }
      return;
    }

    $lang = strtolower($lang);

    $allowedLower = is_array( self::$allowed_langs ) ? array_map( 'strtolower', self::$allowed_langs ) : array();
    if ( ! empty( $allowedLower ) && ! in_array( $lang, $allowedLower, true ) ) {
      return;
    }

    $legacy_locale = function_exists( 'awxct_canon_locale' ) ? awxct_canon_locale( $lang ) : str_replace( '_', '-', $lang );

    // Default language must NOT be persisted in awxct_lang cookie.
    if ( $def && $lang === $def ) {
      if ( self::get_cookie_lang() !== '' ) {
        self::clear_lang_cookie();
      }
    } else {
      $cur = self::get_cookie_lang();
      if ($cur !== $lang) {
        setcookie(
          'awxct_lang',
          $lang,
          time() + 60 * 60 * 24 * 30,
          COOKIEPATH ?: '/',
          COOKIE_DOMAIN ?: '',
          is_ssl(),
          true
        );

        // For current request.
        $_COOKIE['awxct_lang'] = $lang;
      }
    }

    $awxct_set_lang = isset( $_GET['awxct_set_lang'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['awxct_set_lang'] ) ) : '';
    $nonce        = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['_wpnonce'] ) ) : '';
    if ( '' !== $awxct_set_lang && $nonce && wp_verify_nonce( $nonce, 'awxct_set_lang' ) && ! headers_sent() ) {
      $clean = remove_query_arg( array( 'awxct_set_lang', '_wpnonce' ) );
      wp_safe_redirect( $clean ? $clean : home_url( '/' ), 302 );
      exit;
    }
  }

  /**
   * Clear language cookie for all relevant path variants.
   * (Do not persist default language.)
   */
  protected static function clear_lang_cookie(): void
  {
    if ( headers_sent() ) {
      return;
    }

    $secure = is_ssl();
    $domain = COOKIE_DOMAIN ?: '';

    // 1) WordPress COOKIEPATH
    setcookie( 'awxct_lang', '', time() - 3600, COOKIEPATH ?: '/', $domain, $secure, true );
    // 2) Root path (in case JS set it)
    if ( (COOKIEPATH ?: '/') !== '/' ) {
      setcookie( 'awxct_lang', '', time() - 3600, '/', $domain, $secure, true );
    }

    // Also remove for current request.
    unset( $_COOKIE['awxct_lang'] );
  }

  public static function maybe_redirect_to_active_lang()
  {
    if (is_admin() || is_feed() || defined('REST_REQUEST')) {
      return;
    }

    $uri = self::get_request_uri();
    if ($uri === '') {
      return;
    }

    $home = home_url('/');
    $homePath = wp_parse_url($home, PHP_URL_PATH);
    if (!is_string($homePath)) {
      $homePath = '/';
    }

    $path = wp_parse_url($uri, PHP_URL_PATH);
    if (!is_string($path)) {
      return;
    }

    $rel = $path;
    if ($homePath && $homePath !== '/' && strpos($rel, $homePath) === 0) {
      $rel = substr($rel, strlen($homePath));
    }
    $rel = '/' . ltrim($rel, '/');

    // IMPORTANT: do NOT redirect '/' to a cookie language.
    // Default language must stay at '/', and cookie must never force redirects.

    $segments = array_values(array_filter(explode('/', trim($rel, '/'))));
    if (count($segments) < 2) {
      return;
    }

    $prefix = strtolower($segments[0]);
    $slug = strtolower($segments[1]);

    $allowedLower = is_array(self::$allowed_langs)
      ? array_map('strtolower', self::$allowed_langs)
      : [];

    if (!in_array($prefix, $allowedLower, true)) {
      return;
    }

    if (!preg_match('~^home-[a-z]{2}-[a-z]{2}$~', $slug)) {
      return;
    }

    $def = self::get_default_lang_safe();

    if ($prefix === $def) {
      $targetUrl = trailingslashit(home_url('/'));
    } else {
      $targetUrl = trailingslashit(home_url('/' . $prefix . '/'));
    }

    $currentFull = trailingslashit(home_url($rel));
    if ($targetUrl === $currentFull) {
      return;
    }

    self::debug_log(
      '[AWX Routing REDIR] home-alias uri=' . $uri
      . ' prefix=' . $prefix
      . ' slug=' . $slug
      . ' -> ' . $targetUrl
    );

    wp_safe_redirect($targetUrl, 301);
    exit;
  }

  protected static function resolve_translation_post_id($original_id, $lang)
  {
    global $wpdb;

    $original_id = (int) $original_id;
    $lang_db = strtolower(str_replace('_', '-', trim($lang)));

    if ($original_id <= 0 || $lang_db === '') {
      return 0;
    }

    $tbl = defined('AWXCT_TABLE_TRANSLATIONS')
      ? AWXCT_TABLE_TRANSLATIONS
      : $wpdb->prefix . 'awxct_translations';

    $tbl_ident = self::validate_table_ident($tbl);
    $tbl_sql = self::table_sql($tbl);

    if ($tbl_ident === '' || $tbl_sql === '') {
      return 0;
    }

    $cache_key = 'awxct_resolve_' . $original_id;
    $rows = wp_cache_get($cache_key, self::$cache_group);

    if (false === $rows) {
      $sql = "SELECT translated_post_id, language_code
         FROM `".$tbl_sql."`
         WHERE original_post_id = %d AND COALESCE(is_active,0)=1";
      // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table identifier validated (regex) + esc_sql(); values prepared; cached below
      $rows = (array) $wpdb->get_results($wpdb->prepare($sql, $original_id), ARRAY_A);
      wp_cache_set($cache_key, $rows, self::$cache_group, 300);
    }
    $target_id = 0;

    if (!empty($rows)) {
      foreach ($rows as $row) {
        $code = strtolower(str_replace('_', '-', $row['language_code'] ?? ''));
        $tid = (int) ($row['translated_post_id'] ?? 0);

        self::debug_log('[AWX Routing RESOLVE] orig_id=' . $original_id . ' row_code=' . $code . ' post_id=' . $tid);

        if ($code === $lang_db && $tid > 0) {
          $target_id = $tid;
          break;
        }
      }
    }

    if ($target_id === 0 && class_exists('AWXCT_Frontend') && method_exists('AWXCT_Frontend', 'get_translations')) {
      $items = AWXCT_Frontend::get_translations($original_id, false);
      if (is_array($items)) {
        foreach ($items as $it) {
          $code = strtolower(str_replace('_', '-', $it['code'] ?? ''));
          $tid = (int) ($it['post_id'] ?? 0);
          self::debug_log('[AWX Routing RESOLVE Fallback] original_id=' . $original_id . ' code=' . $code . ' post_id=' . $tid);
          if ($code === $lang_db && $tid > 0) {
            $target_id = $tid;
            break;
          }
        }
      }
    }

    return $target_id;
  }


  /**
   * Detect "clone" posts (not real translations).
   * Clones are stored in translations table with an empty language_code.
   *
   * @param int $post_id
   * @return bool
   */
  protected static function is_clone_post($post_id)
  {
    global $wpdb;

    $post_id = (int) $post_id;
    if ($post_id <= 0) {
      return false;
    }

    $tbl = defined('AWXCT_TABLE_TRANSLATIONS')
      ? AWXCT_TABLE_TRANSLATIONS
      : $wpdb->prefix . 'awxct_translations';

    $tbl_ident = self::validate_table_ident($tbl);
    $tbl_sql   = self::table_sql($tbl);

    if ($tbl_ident === '' || $tbl_sql === '') {
      return false;
    }

    $cache_key = 'awxct_is_clone_' . $post_id;
    $cached = wp_cache_get($cache_key, self::$cache_group);
    if (false !== $cached) {
      return (bool) $cached;
    }

    $sql = "SELECT 1 FROM `".$tbl_sql."` WHERE translated_post_id = %d AND (language_code = '' OR language_code IS NULL) LIMIT 1";
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table identifier validated (regex) + esc_sql(); values prepared; cached below
    $hit = (int) $wpdb->get_var($wpdb->prepare($sql, $post_id));

    $is_clone = ($hit === 1);
    wp_cache_set($cache_key, $is_clone ? 1 : 0, self::$cache_group, 300);
    return $is_clone;
  }

  protected static function get_post_lang_code($post_id)
  {
    global $wpdb;

    $post_id = (int) $post_id;
    if ($post_id <= 0) {
      return '';
    }

    $tbl = defined('AWXCT_TABLE_TRANSLATIONS')
      ? AWXCT_TABLE_TRANSLATIONS
      : $wpdb->prefix . 'awxct_translations';

    $tbl_ident = self::validate_table_ident($tbl);
    $tbl_sql = self::table_sql($tbl);

    if ($tbl_ident === '' || $tbl_sql === '') {
      return self::get_default_lang_safe();
    }

    $cache_key = 'awxct_post_lang_' . $post_id;
    $row = wp_cache_get($cache_key, self::$cache_group);

    if (false === $row) {
      $sql = "SELECT language_code FROM `".$tbl_sql."` WHERE translated_post_id = %d LIMIT 1";
      // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- table identifier validated (regex) + esc_sql(); values prepared; cached below
      $row = $wpdb->get_row($wpdb->prepare($sql, $post_id), ARRAY_A);
      wp_cache_set($cache_key, $row, self::$cache_group, 300);
    }

    if (!empty($row['language_code'])) {
      $code = strtolower(str_replace('_', '-', $row['language_code']));
      return $code;
    }

    return self::get_default_lang_safe();
  }
}

AWXCT_Language_Routing::init();
