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

class AWXCT_Frontend {

	private static $tbl_trans;
	private static $tbl_lang;

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

	/**
	 * Validate a table identifier (alnum + underscore only).
	 *
	 * @param string $table
	 * @return string Empty string if invalid.
	 */
	private 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 name for SQL contexts.
	 *
	 * @param string $table
	 * @return string Empty string if invalid.
	 */
	private static function table_sql( $table ): string {
		$valid = self::validate_table_ident( $table );
		if ( '' === $valid ) {
			return '';
		}
		return esc_sql( $valid );
	}

	/* =========================================================
	 * Switcher placement
	 * ========================================================= */

	private static function normalize_position( string $raw ): string {
		$raw = strtolower( trim( $raw ) );

		// Neue Schlüssel aus der Einstellungsseite
		if ( 'floating_top_left' === $raw ) {
			return 'float-left';
		}
		if ( 'floating_top_right' === $raw ) {
			return 'float-right';
		}
		if ( 'menu_end' === $raw ) {
			return 'menu-end';
		}

		// Ältere Schreibweisen / Fallbacks
		switch ( $raw ) {
			case 'menu':
			case 'inside_menu':
			case 'inside-main-menu':
			case 'inside main menu':
			case 'menu-end':
				return 'menu-end';

			case 'float_right':
			case 'floating-right':
			case 'right':
			case 'float-right':
				return 'float-right';

			case 'float_left':
			case 'floating-left':
			case 'left':
			case 'float-left':
			default:
				return 'float-left';
		}
	}

	private static function get_switcher_placement(): string {
		$raw       = get_option( 'awxct_switcher_position', 'floating_top_left' );
		$placement = self::normalize_position( (string) $raw );

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			do_action( 'awxct_debug_log', '[AWXCT] raw placement option (position) = ' . ( is_scalar( $raw ) ? (string) $raw : '' ) );
			do_action( 'awxct_debug_log', '[AWXCT] placement (PHP normalized) = ' . $placement );
		}

		return (string) apply_filters( 'awxct_switcher_placement', $placement, $raw );
	}

	/* =========================================================
	 * Request helpers
	 * ========================================================= */

private static function is_editor_like_request(): bool {
	if ( is_admin() ) {
		return true;
	}
	if ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) {
		return true;
	}
	if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
		return true;
	}
	if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) {
		return true;
	}
	if ( is_preview() ) {
		return true;
	}

	$raw_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '/';
	$path    = wp_parse_url( $raw_uri, PHP_URL_PATH );
	$path    = is_string( $path ) ? $path : '/';

	if ( stripos( $path, '/wp-admin/' ) === 0 ) {
		return true;
	}

		// Read-only checks for editor/preview contexts; no state is changed here.
	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only query var checks (preview/editor guards), no form processing.
	if ( isset( $_GET['preview'] ) || isset( $_GET['preview_id'] ) || isset( $_GET['post_preview'] ) || isset( $_GET['elementor-preview'] ) ) {
		return true;
	}

	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only query var check, no form processing.
	if ( isset( $_GET['iframe'] ) ) {
		return true;
	}

	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only query var check, no form processing.
	$action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['action'] ) ) : '';
	if ( '' !== $action ) {
		return true;
	}

	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only query var check, no form processing.
	$context = isset( $_GET['context'] ) ? strtolower( sanitize_text_field( wp_unslash( (string) $_GET['context'] ) ) ) : '';
	if ( 'edit' === $context ) {
		return true;
	}

	return false;
}











	private static function has_col( $table, $col ): bool {
		global $wpdb;

		$table_sql = self::table_sql( $table );
		if ( '' === $table_sql ) {
			return false;
		}

		$cache_key = 'awxct_cols_' . md5( $table_sql );
		$cols      = wp_cache_get( $cache_key, self::$cache_group );

		if ( false === $cols ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Schema inspection; table identifier validated (alnum/_), escaped via esc_sql(); result cached.
			$cols = (array) $wpdb->get_col( "SHOW COLUMNS FROM `{$table_sql}`", 0 );
			wp_cache_set( $cache_key, $cols, self::$cache_group, 300 );
		}

		return in_array( (string) $col, $cols, true );
	}

	public static function init() {
		global $wpdb;

		self::$tbl_trans = $wpdb->prefix . 'awxct_translations';
		self::$tbl_lang  = $wpdb->prefix . 'awxct_languages';

		add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
		add_action( 'wp_body_open', array( __CLASS__, 'inject_switcher_container' ), 100 );
		add_action( 'wp_footer', array( __CLASS__, 'inject_switcher_container' ), 100 );

		add_shortcode( 'awxct_language_switcher', array( __CLASS__, 'shortcode_language_switcher' ) );
		add_filter( 'wp_nav_menu_items', array( __CLASS__, 'inject_switcher_placeholder' ), 9999, 2 );
		add_action( 'wp_head', array( __CLASS__, 'output_hreflang_links' ), 5 );

		add_action( 'template_redirect', array( __CLASS__, 'handle_clone_redirect_or_notice' ), 0 );
	}

	/* =========================================================
	 * Assets
	 * ========================================================= */

	public static function enqueue_assets() {
		if ( self::is_editor_like_request() ) {
			return;
		}

		$base_dir = plugin_dir_path( AWXCT_FILE );
		$base_url = plugin_dir_url( AWXCT_FILE );

		$rel_css = 'assets/css/frontend.css';
		$url_css = function_exists( 'awxct_get_style_url' ) ? awxct_get_style_url( $rel_css ) : $base_url . $rel_css;
		$ver_css = defined( 'AWXCT_VERSION' ) ? AWXCT_VERSION : null;

		wp_enqueue_style( 'awxct-plg-ct-frontend', $url_css, array(), $ver_css );

		$custom_css = trim( (string) get_option( 'awxct_switcher_custom_css', '' ) );
		if ( '' !== $custom_css ) {
			wp_add_inline_style( 'awxct-plg-ct-frontend', $custom_css );
		}

		$fi_rel         = 'assets/vendor/flag-icons/css/flag-icons.min.css';
		$fi_path        = $base_dir . $fi_rel;
		$fi_url         = $base_url . $fi_rel;
		$has_flag_icons = file_exists( $fi_path );

		if ( $has_flag_icons ) {
			$fi_ver = (string) filemtime( $fi_path );
			wp_enqueue_style( 'awxct-plg-ct-flag-icons', $fi_url, array(), $fi_ver );
		}

		$rel_js = 'assets/js/frontend.js';
		$url_js = function_exists( 'awxct_get_script_url' ) ? awxct_get_script_url( $rel_js ) : $base_url . $rel_js;
		$ver_js = defined( 'AWXCT_VERSION' ) ? AWXCT_VERSION : null;

		wp_register_script( 'awxct-plg-ct-frontend', $url_js, array(), $ver_js, true );

		$langs = self::get_all_languages();
		$urls  = self::build_language_urls();
		$def   = self::get_default_lang();
		$cur   = self::detect_current_language( $langs, $def );

		$allowed = array();
		foreach ( $langs as $l ) {
			$code = strtolower( (string) ( $l['code'] ?? '' ) );
			if ( '' === $code ) {
				continue;
			}
			$allowed[] = $code;
			$short     = strtolower( (string) ( preg_split( '/[-_]/', $code )[0] ?? $code ) );
			$allowed[] = $short;
		}
		$allowed = array_values( array_unique( $allowed ) );

		$label_style = (string) get_option( 'awxct_switcher_label_style', 'flag+lang' );
		$placement   = self::get_switcher_placement();
		$flags_url   = $base_url . 'assets/flags/';

		$cfg_base  = function_exists( 'awxct_get_config' ) ? awxct_get_config() : array();
		$cfg_local = is_array( $cfg_base ) ? $cfg_base : array();
		// Frontend debug logs should be off by default and follow WP_DEBUG.
		$cfg_local['debug'] = (bool) ( defined( 'WP_DEBUG' ) && WP_DEBUG );

		$home_path = (string) wp_parse_url( home_url( '/' ), PHP_URL_PATH );
		$home_path = trailingslashit( $home_path ?: '/' );


		wp_localize_script(
			'awxct-plg-ct-frontend',
			'AWXCT',
			array(
				'languages'  => $langs,
				'urls'       => $urls,
				'current'    => $cur,
				'default'    => $def,
				'allowed'    => $allowed,
				'labelStyle' => $label_style,
				'placement'  => $placement,
				'flagsUrl'   => $flags_url,
				'flagsLib'   => $has_flag_icons ? 'flag-icons' : 'fallback',
				'homePath'   => $home_path,
				'cfg'        => $cfg_local,
				'setLangNonce' => wp_create_nonce( 'awxct_set_lang' ),
			)
		);

		wp_enqueue_script( 'awxct-plg-ct-frontend' );
	}

	/* =========================================================
	 * Switcher container
	 * ========================================================= */

	public static function inject_switcher_container() {
		$placement = self::get_switcher_placement();

		if ( ! did_action( 'awxct_switcher_root_printed' ) ) {
			$classes = array( 'awxct-plg-ct-switcher-root' );
			if ( 'float-right' === $placement ) {
				$classes[] = 'awxct-plg-ct-pos-right';
			} else {
				$classes[] = 'awxct-plg-ct-pos-left';
			}

			echo '<div id="awxct-plg-ct-switcher-root" class="' . esc_attr( implode( ' ', $classes ) ) . '" aria-live="polite"></div>';
			do_action( 'awxct_switcher_root_printed' );
		}
	}

	/* =========================================================
	 * Defaults & helpers
	 * ========================================================= */

	public static function get_default_lang(): string {
		global $wpdb;

		$tbl_sql = self::table_sql( self::$tbl_lang );
		if ( '' === $tbl_sql ) {
			return 'en';
		}

		$cache_key = 'awxct_default_lang';
		$cached    = wp_cache_get( $cache_key, self::$cache_group );
		if ( false !== $cached && is_string( $cached ) && '' !== $cached ) {
			return $cached;
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier validated (alnum/_), escaped via esc_sql(); no user input.
		$code = (string) $wpdb->get_var( "SELECT LOWER(code) FROM `{$tbl_sql}` WHERE COALESCE(is_default,0)=1 LIMIT 1" );
		$code = strtolower( trim( $code ) );
		if ( '' !== $code ) {
			wp_cache_set( $cache_key, $code, self::$cache_group, 300 );
			return $code;
		}

		$col_name = self::has_col( self::$tbl_lang, 'english_name' ) ? 'english_name' : ( self::has_col( self::$tbl_lang, 'native_name' ) ? 'native_name' : 'code' );
		$allowed  = array( 'english_name', 'native_name', 'code' );
		if ( ! in_array( $col_name, $allowed, true ) ) {
			$col_name = 'code';
		}

		$cache_key2 = 'awxct_first_active_' . $col_name;
		$cached2    = wp_cache_get( $cache_key2, self::$cache_group );
		if ( false !== $cached2 && is_string( $cached2 ) && '' !== $cached2 ) {
			return $cached2;
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier validated (alnum/_), escaped via esc_sql(); column name whitelisted; no user input.
		$code = (string) $wpdb->get_var( "SELECT LOWER(code) FROM `{$tbl_sql}` WHERE COALESCE(active,0)=1 ORDER BY `{$col_name}` ASC LIMIT 1" );
		$code = strtolower( trim( $code ) );
		if ( '' !== $code ) {
			wp_cache_set( $cache_key2, $code, self::$cache_group, 300 );
			return $code;
		}

		return 'en';
	}

	public static function get_all_languages( bool $only_active = true ): array {
		global $wpdb;

		$tbl_sql = self::table_sql( self::$tbl_lang );
		if ( '' === $tbl_sql ) {
			return array();
		}

		$has_native  = self::has_col( self::$tbl_lang, 'native_name' );
		$has_english = self::has_col( self::$tbl_lang, 'english_name' );

		$order_col = $has_native ? 'native_name' : ( $has_english ? 'english_name' : 'code' );
		$allowed   = array( 'native_name', 'english_name', 'code' );
		if ( ! in_array( $order_col, $allowed, true ) ) {
			$order_col = 'code';
		}

		$cache_key = 'awxct_langs_' . $order_col . '_' . (int) $has_native . '_' . (int) $has_english . '_' . (int) $only_active;
		$rows      = wp_cache_get( $cache_key, self::$cache_group );

		if ( false === $rows ) {
			$select_native  = $has_native ? 'native_name' : "'' AS native_name";
			$select_english = $has_english ? 'english_name' : "'' AS english_name";

			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- validated table + whitelisted column
			$sql = "
				SELECT LOWER(code) AS code,
					   COALESCE(active, 0) AS active,
					   COALESCE(is_default, 0) AS is_default,
					   {$select_native},
					   {$select_english}
				  FROM `{$tbl_sql}`
			  ORDER BY `{$order_col}` ASC, code ASC
			";

			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query uses only validated/whitelisted identifiers; no user input; results cached.
			$rows = (array) $wpdb->get_results( $sql, ARRAY_A );
			wp_cache_set( $cache_key, $rows, self::$cache_group, 300 );
		}

		if ( ! $rows || ! is_array( $rows ) ) {
			$rows = array(
				array(
					'code'         => 'de',
					'active'       => 1,
					'is_default'   => 1,
					'native_name'  => 'Deutsch',
					'english_name' => 'German',
				),
				array(
					'code'         => 'en',
					'active'       => 1,
					'is_default'   => 0,
					'native_name'  => 'Englisch',
					'english_name' => 'English',
				),
			);
		}

		foreach ( $rows as &$r ) {
			$r['code']         = strtolower( (string) ( $r['code'] ?? '' ) );
			$r['active']       = (int) ( $r['active'] ?? 0 );
			$r['is_default']   = (int) ( $r['is_default'] ?? 0 );
			$r['native_name']  = isset( $r['native_name'] ) ? (string) $r['native_name'] : '';
			$r['english_name'] = isset( $r['english_name'] ) ? (string) $r['english_name'] : '';
			$r['name']         = ( '' !== $r['native_name'] ) ? $r['native_name'] : ( ( '' !== $r['english_name'] ) ? $r['english_name'] : $r['code'] );
			$r['default']      = $r['is_default'];
		}
		unset( $r );

		if ( $only_active ) {
			$rows = array_values(
				array_filter(
					$rows,
					static function ( $r ) {
						return ! empty( $r['active'] );
					}
				)
			);
		}

		
// Ensure deterministic switcher order:
// - Default language always first
// - Remaining languages sorted alphabetically by code (stable, not dependent on current page)
$def_code = '';
foreach ( $rows as $rr ) {
    if ( ! empty( $rr['is_default'] ) || ! empty( $rr['default'] ) ) {
        $def_code = strtolower( (string) ( $rr['code'] ?? '' ) );
        break;
    }
}
if ( '' === $def_code ) {
    $def_code = strtolower( (string) self::get_default_lang() );
}

usort(
    $rows,
    static function ( $a, $b ) use ( $def_code ) {
        $ac = strtolower( (string) ( $a['code'] ?? '' ) );
        $bc = strtolower( (string) ( $b['code'] ?? '' ) );

        if ( '' !== $def_code ) {
            if ( $ac === $def_code && $bc !== $def_code ) {
                return -1;
            }
            if ( $bc === $def_code && $ac !== $def_code ) {
                return 1;
            }
        }

        return strcmp( $ac, $bc );
    }
);

return (array) apply_filters( 'awxct_enabled_languages', $rows, $only_active );
	}

	public static function detect_current_language( array $langs, string $def ): string {
		$q = get_query_var( 'awxct_lang' );
		if ( ! empty( $q ) ) {
			return strtolower( (string) $q );
		}

		$raw_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '/';
		$req     = trailingslashit( sanitize_text_field( $raw_uri ) );

		$cur = $def;
		foreach ( $langs as $l ) {
			$code = strtolower( (string) ( $l['code'] ?? '' ) );
			if ( '' === $code ) {
				continue;
			}
			if ( strtolower( $def ) !== $code && preg_match( '#/' . preg_quote( $code, '#' ) . '/#', $req ) ) {
				$cur = $code;
				break;
			}
		}
		return $cur;
	}

	public static function get_original_id( $post_id = null ): int {
		global $wpdb;

		$post_id = $post_id ? (int) $post_id : (int) get_queried_object_id();
		if ( ! $post_id ) {
			return 0;
		}

		$tbl_trans_sql = self::table_sql( self::$tbl_trans );
		if ( '' === $tbl_trans_sql ) {
			return 0;
		}

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

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Validated table identifier is interpolated.
		$sql  = "SELECT original_post_id FROM `{$tbl_trans_sql}` WHERE translated_post_id = %d LIMIT 1";
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier validated (alnum/_), escaped via esc_sql(); values prepared.
		$orig = (int) $wpdb->get_var( $wpdb->prepare( $sql, $post_id ) );
		if ( $orig ) {
			wp_cache_set( $cache_key, $orig, self::$cache_group, 300 );
			return $orig;
		}

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Validated table identifier is interpolated.
		$sql   = "SELECT original_post_id FROM `{$tbl_trans_sql}` WHERE original_post_id = %d LIMIT 1";
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier validated (alnum/_), escaped via esc_sql(); values prepared.
		$maybe = (int) $wpdb->get_var( $wpdb->prepare( $sql, $post_id ) );
		if ( $maybe ) {
			wp_cache_set( $cache_key, $post_id, self::$cache_group, 300 );
			return $post_id;
		}

		wp_cache_set( $cache_key, 0, self::$cache_group, 300 );
		return 0;
	}

	public static function get_translations( int $original_id, bool $include_unavailable = false ): array {
		global $wpdb;

		$original_id = (int) $original_id;
		if ( ! $original_id ) {
			return array();
		}

		$cache_key = 'awxct_trans_' . $original_id . '_' . (int) $include_unavailable;
		$cached    = wp_cache_get( $cache_key, self::$cache_group );
		if ( false !== $cached && is_array( $cached ) ) {
			return $cached;
		}

		$tbl_trans_sql = self::table_sql( self::$tbl_trans );
		if ( '' === $tbl_trans_sql ) {
			return array();
		}

		$langs = self::get_all_languages();
		$def   = self::get_default_lang();

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Validated table identifier is interpolated.
		$sql  = "SELECT language_code, translated_post_id, is_active
				   FROM `{$tbl_trans_sql}`
				  WHERE original_post_id = %d";
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table identifier validated (alnum/_), escaped via esc_sql(); values prepared.
		$rows = (array) $wpdb->get_results( $wpdb->prepare( $sql, $original_id ), ARRAY_A );

		$by_code = array();
		foreach ( $rows as $r ) {
			if ( ! isset( $r['language_code'] ) ) {
				continue;
			}
			$by_code[ strtolower( (string) $r['language_code'] ) ] = $r;
		}

		$current_id = (int) get_queried_object_id();
		$list       = array();

		foreach ( $langs as $l ) {
			$code = strtolower( (string) ( $l['code'] ?? '' ) );
			if ( '' === $code ) {
				continue;
			}

			$name    = (string) ( $l['name'] ?? $code );
			$native  = (string) ( $l['native_name'] ?? '' );
			$english = (string) ( $l['english_name'] ?? '' );

			if ( $code === strtolower( $def ) ) {
				$post_id   = $original_id;
				$is_active = 1;
			} else {
				$post_id   = isset( $by_code[ $code ] ) ? (int) $by_code[ $code ]['translated_post_id'] : 0;
				$is_active = isset( $by_code[ $code ] ) ? (int) $by_code[ $code ]['is_active'] : 0;
			}

			if ( ! $include_unavailable && ( ! $post_id || ! $is_active ) ) {
				continue;
			}

			$url        = ( $post_id && $is_active ) ? self::canonical_url_for( $original_id, $code ) : '';
			$is_current = ( $post_id && $current_id && $post_id === $current_id );

			$list[] = array(
				'code'         => $code,
				'name'         => $name,
				'native_name'  => $native,
				'english_name' => $english,
				'post_id'      => (int) $post_id,
				'url'          => (string) $url,
				'is_active'    => (int) $is_active,
				'is_current'   => $is_current ? 1 : 0,
				'is_default'   => ( $code === strtolower( $def ) ) ? 1 : 0,
			);
		}

		wp_cache_set( $cache_key, $list, self::$cache_group, 300 );
		return $list;
	}

	protected static function build_language_urls(): array {
		$current_id  = (int) get_queried_object_id();
		$original_id = (int) self::get_original_id( $current_id );
		if ( ! $original_id ) {
			$original_id = $current_id ?: 0;
		}

		$langs = self::get_all_languages();
		$map   = array();

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

		$def = strtolower( str_replace( '_', '-', self::get_default_lang() ?: '' ) );

		foreach ( $langs as $l ) {
			$code_raw = isset( $l['code'] ) ? (string) $l['code'] : '';
			$code     = strtolower( str_replace( '_', '-', trim( $code_raw ) ) );
			if ( '' === $code ) {
				continue;
			}

			if ( $front_root && $original_id === $front_root ) {
				if ( $code === $def ) {
					$map[ $code ] = trailingslashit( home_url( '/' ) );
				} else {
					$map[ $code ] = trailingslashit( home_url( '/' . $code . '/' ) );
				}
				continue;
			}

			$map[ $code ] = (string) self::canonical_url_for( $original_id, $code );
		}

		return $map;
	}

	private static function canonical_url_for( int $original_id, string $lang_code ): ?string {
		$orig_url = get_permalink( $original_id );
		if ( ! $orig_url ) {
			return null;
		}

		$lang = strtolower( trim( $lang_code ) );
		$def  = self::get_default_lang();

		$home = trailingslashit( home_url( '/' ) );
		$path = ltrim( str_replace( $home, '', trailingslashit( $orig_url ) ), '/' );

		$allowed = array();
		foreach ( self::get_all_languages( true ) as $l ) {
			$code = strtolower( (string) ( $l['code'] ?? '' ) );
			if ( '' === $code ) {
				continue;
			}
			$allowed[] = $code;
			$allowed[] = strtolower( (string) ( preg_split( '/[-_]/', $code )[0] ?? $code ) );
		}
		$allowed = array_values( array_unique( $allowed ) );

		$segments = explode( '/', trim( $path, '/' ) );
		$clean    = array();
		foreach ( $segments as $seg ) {
			$seg = (string) $seg;
			if ( '' === $seg ) {
				continue;
			}
			$clean[] = $seg;
		}
		while ( ! empty( $clean ) && in_array( strtolower( (string) $clean[0] ), $allowed, true ) ) {
			array_shift( $clean );
		}

		$rel = implode( '/', $clean );
		if ( '' !== $rel && '/' !== substr( $rel, -1 ) ) {
			$rel .= '/';
		}

		$target = ( $lang === strtolower( $def ) )
			? $home . ltrim( $rel, '/' )
			: trailingslashit( $home . $lang ) . $rel;

		$target = preg_replace( '#(?<!:)//+#', '/', (string) $target );
		return (string) $target;
	}

	/* =========================================================
	 * Labels / Flags
	 * ========================================================= */

	private static function format_lang_label( array $it ): string {
		$style     = (string) get_option( 'awxct_switcher_label_style', 'flag+lang' );
		$code_full = strtolower( (string) ( $it['code'] ?? '' ) );
		$parts     = preg_split( '/[-_]/', $code_full );
		$langShort = strtoupper( (string) ( $parts[0] ?? '' ) );
		$country   = strtoupper( (string) ( $parts[1] ?? ( $parts[0] ?? '' ) ) );
		$native    = (string) ( $it['native_name'] ?? ( $it['name'] ?? '' ) );
		$english   = (string) ( $it['english_name'] ?? '' );

		$flagHtml = self::flag_html( $country );

		switch ( $style ) {
			case 'lang':
				return esc_html( $langShort );
			case 'native':
				return esc_html( '' !== $native ? $native : $langShort );
			case 'english':
				return esc_html( '' !== $english ? $english : $langShort );
			case 'flag':
				return $flagHtml;
			case 'flag+lang':
				return $flagHtml . ' <span class="awxct-plg-ct-label">' . esc_html( $langShort ) . '</span>';
			case 'flag+native':
				return $flagHtml . ' <span class="awxct-plg-ct-label">' . esc_html( '' !== $native ? $native : $langShort ) . '</span>';
			case 'flag+english':
				return $flagHtml . ' <span class="awxct-plg-ct-label">' . esc_html( '' !== $english ? $english : $langShort ) . '</span>';
			case 'locale':
			default:
				return esc_html( strtoupper( str_replace( '_', '-', $code_full ) ) );
		}
	}

	private static function flag_html( string $country ): string {
		$country = strtoupper( $country ?: 'UN' );

		$base_path = plugin_dir_path( dirname( __FILE__ ) );
		$fi_css    = $base_path . 'assets/vendor/flag-icons/css/flag-icons.min.css';

		if ( file_exists( $fi_css ) && preg_match( '/^[A-Z]{2}$/', $country ) ) {
			return '<span class="fi awxct-plg-ct-flag fi-' . esc_attr( strtolower( $country ) ) . '" aria-hidden="true"></span>';
		}

		$rel  = 'assets/flags/' . strtolower( $country ) . '.svg';
		$path = $base_path . $rel;
		$url  = plugin_dir_url( dirname( __FILE__ ) ) . $rel;

		if ( file_exists( $path ) ) {
			return '<img class="awxct-plg-ct-flag" src="' . esc_url( $url ) . '" alt="' . esc_attr( $country ) . '" width="18" height="14" loading="lazy">';
		}

		// Emoji fallback
		if ( ! preg_match( '/^[A-Z]{2}$/', $country ) ) {
			return '<span class="awxct-plg-ct-flag-emoji" aria-hidden="true">🏳️</span>';
		}

		$offset = 0x1F1E6;
		$A      = ord( 'A' );
		$cp1    = $offset + ( ord( $country[0] ) - $A );
		$cp2    = $offset + ( ord( $country[1] ) - $A );

		$toUtf8 = static function ( int $cp ): string {
			return html_entity_decode( '&#' . $cp . ';', ENT_NOQUOTES, 'UTF-8' );
		};

		return '<span class="awxct-plg-ct-flag-emoji" aria-hidden="true">' . $toUtf8( $cp1 ) . $toUtf8( $cp2 ) . '</span>';
	}

	/* =========================================================
	 * Shortcode / rendering / hreflang
	 * ========================================================= */

	public static function shortcode_language_switcher( $atts ) {
		$atts = shortcode_atts(
			array(
				'type'             => 'dropdown',
				'show_unavailable' => '0',
				'show_names'       => '1',
				'show_codes'       => '0',
				'debug'            => '0',
			),
			$atts,
			'awxct_language_switcher'
		);

		$html = self::render_switcher(
			array(
				'type'               => (string) $atts['type'],
				'include_unavailable' => ( '1' === (string) $atts['show_unavailable'] ),
				'show_names'          => ( '1' === (string) $atts['show_names'] ),
				'show_codes'          => ( '1' === (string) $atts['show_codes'] ),
			)
		);

		if ( '' !== $html || '1' !== (string) $atts['debug'] || ! current_user_can( 'manage_options' ) ) {
			return $html;
		}

		$qid   = (int) get_queried_object_id();
		$oid   = (int) self::get_original_id();
		$items = $oid ? self::get_translations( $oid, true ) : array();

		ob_start();
		?>
		<div class="awxct-plg-ct-debug" style="padding:.75rem;border:1px dashed #999;font:14px/1.4 system-ui;">
			<strong>AWXCT Debug:</strong>
			<ul style="margin:.5rem 0 .25rem 1.2rem;">
				<li>queried_object_id: <code><?php echo (int) $qid; ?></code></li>
				<li>original_id: <code><?php echo (int) $oid; ?></code></li>
				<li>translations: <code><?php echo (int) count( $items ); ?></code></li>
			</ul>
		</div>
		<?php
		return trim( (string) ob_get_clean() );
	}

	public static function render_switcher( array $args = array() ): string {
		$defaults = array(
			'type'               => 'dropdown',
			'include_unavailable' => false,
			'show_names'          => true,
			'show_codes'          => false,
		);
		$args = wp_parse_args( $args, $defaults );

		$original_id = self::get_original_id();
		if ( ! $original_id ) {
			return '';
		}

		$items = self::get_translations( (int) $original_id, (bool) $args['include_unavailable'] );
		if ( ! $items ) {
			$items = self::get_translations( (int) $original_id, true );
			if ( ! $items ) {
				return '';
			}
		}

		$current = null;
		foreach ( $items as $it ) {
			if ( ! empty( $it['is_current'] ) ) {
				$current = $it;
				break;
			}
		}
		if ( ! $current ) {
			$current = $items[0];
		}

		if ( 'inline' === (string) $args['type'] ) {
			return self::render_inline( $items, $args );
		}
		return self::render_dropdown( $items, $current, $args );
	}

	private static function label_for( array $it ): string {
		return self::format_lang_label( $it );
	}

	private static function render_dropdown( array $items, array $current, array $args ): string {
		$is_bootstrap = wp_style_is( 'bootstrap', 'enqueued' ) || wp_style_is( 'bootstrap', 'registered' );

		ob_start();
		if ( $is_bootstrap ) :
			?>
			<div class="awxct-plg-ct-lang-switcher dropdown">
				<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
					<?php echo wp_kses_post( self::label_for( $current ) ); ?>
				</button>
				<ul class="dropdown-menu">
					<?php foreach ( $items as $it ) : ?>
						<?php
						$label    = self::label_for( $it );
						$disabled = empty( $it['url'] );
						$classes  = 'dropdown-item' . ( ! empty( $it['is_current'] ) ? ' active' : '' );
						?>
						<li>
							<?php if ( $disabled ) : ?>
								<span class="<?php echo esc_attr( $classes ); ?> disabled" aria-disabled="true"><?php echo wp_kses_post( $label ); ?></span>
							<?php else : ?>
								<a class="<?php echo esc_attr( $classes ); ?>" href="<?php echo esc_url( (string) $it['url'] ); ?>"><?php echo wp_kses_post( $label ); ?></a>
							<?php endif; ?>
						</li>
					<?php endforeach; ?>
				</ul>
			</div>
		<?php else : ?>
			<details class="awxct-plg-ct-lang-switcher awxct-plg-ct-details">
				<summary><?php echo wp_kses_post( self::label_for( $current ) ); ?></summary>
				<ul>
					<?php foreach ( $items as $it ) : ?>
						<li class="<?php echo esc_attr( ! empty( $it['is_current'] ) ? 'current' : '' ); ?>">
							<?php
							$label = self::label_for( $it );
							if ( ! empty( $it['url'] ) ) {
								echo '<a href="' . esc_url( (string) $it['url'] ) . '">' . wp_kses_post( $label ) . '</a>';
							} else {
								echo '<span class="disabled" aria-disabled="true">' . wp_kses_post( $label ) . '</span>';
							}
							?>
						</li>
					<?php endforeach; ?>
				</ul>
			</details>
		<?php endif; ?>

		<?php
		return trim( (string) ob_get_clean() );
	}

	private static function render_inline( array $items, array $args ): string {
		ob_start();
		?>
		<nav class="awxct-plg-ct-lang-switcher awxct-plg-ct-inline" aria-label="<?php esc_attr_e( 'Language switcher', 'anywarex-clone-translate' ); ?>">
			<ul>
				<?php foreach ( $items as $it ) : ?>
					<li class="<?php echo esc_attr( ! empty( $it['is_current'] ) ? 'current' : '' ); ?>">
						<?php
						$label = self::label_for( $it );
						if ( ! empty( $it['url'] ) ) {
							echo '<a href="' . esc_url( (string) $it['url'] ) . '">' . wp_kses_post( $label ) . '</a>';
						} else {
							echo '<span class="disabled" aria-disabled="true">' . wp_kses_post( $label ) . '</span>';
						}
						?>
					</li>
				<?php endforeach; ?>
			</ul>
		</nav>
		<?php
		return trim( (string) ob_get_clean() );
	}

	public static function output_hreflang_links() {
		if ( ! is_singular() ) {
			return;
		}
		$original_id = self::get_original_id();
		if ( ! $original_id ) {
			return;
		}

		$items = self::get_translations( (int) $original_id, false );
		if ( ! $items ) {
			return;
		}

		foreach ( $items as $it ) {
			if ( empty( $it['url'] ) ) {
				continue;
			}
			$code = strtolower( (string) ( $it['code'] ?? '' ) );
			printf(
				"<link rel=\"alternate\" hreflang=\"%s\" href=\"%s\" />\n",
				esc_attr( $code ),
				esc_url( (string) $it['url'] )
			);
		}

		$xdef = self::canonical_url_for( (int) $original_id, self::get_default_lang() );
		if ( $xdef ) {
			printf(
				"<link rel=\"alternate\" hreflang=\"x-default\" href=\"%s\" />\n",
				esc_url( (string) $xdef )
			);
		}
	}

	public static function inject_switcher_placeholder( $items, $args ) {
		$placement = self::get_switcher_placement();
		if ( 'menu-end' !== $placement ) {
			return $items;
		}

		$is_primary_location = isset( $args->theme_location ) && in_array( (string) $args->theme_location, array( 'primary', 'header', 'main' ), true );
		$has_nav_menu_class  = ! empty( $args->menu_class ) && ( strpos( (string) $args->menu_class, 'nav-menu' ) !== false );
		$is_target_menu      = $is_primary_location || $has_nav_menu_class;
		if ( ! $is_target_menu ) {
			return $items;
		}

		if ( strpos( (string) $items, 'id="awxct-plg-ct-switcher-placeholder"' ) !== false ) {
			return $items;
		}

		$li = '<li id="awxct-plg-ct-switcher-placeholder" class="menu-item menu-item-type-custom menu-item-awxct-lang nav-item"></li>';
		return (string) $items . $li;
	}

	/* =========================================================
	 * Redirect/Notice (Elementor-safe guards bleiben!)
	 * ========================================================= */

	public static function handle_clone_redirect_or_notice() {
		if ( is_admin() || ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) ) {
			return;
		}

		// Editor/Preview-Kontexte: nie redirecten (Query-Args wie elementor-preview dürfen nicht verloren gehen)
		if ( self::is_editor_like_request() ) {
			return;
		}

		$awxct_lang = get_query_var( 'awxct_lang' );
		if ( ! empty( $awxct_lang ) ) {
			if ( function_exists( 'awxct_log' ) ) {
				awxct_log( '[AWX FRONT] skip canonical redirect because awxct_lang=' . $awxct_lang );
			}
			return;
		}

		if ( is_front_page() ) {
			if ( function_exists( 'awxct_log' ) ) {
				awxct_log( '[AWX FRONT] skip redirect on front page' );
			}
			return;
		}

		global $wp, $post;

		if ( ! $post || ! is_singular() ) {
			return;
		}

		$current_url = home_url( add_query_arg( array(), $wp->request ) );
		$permalink   = get_permalink( $post );

		if ( ! $current_url || ! $permalink ) {
			return;
		}

		$cur = rtrim( (string) $current_url, '/' );
		$per = rtrim( (string) $permalink, '/' );

		if ( $cur !== $per ) {
			if ( function_exists( 'awxct_trace_redirect' ) ) {
				awxct_trace_redirect( 'wp_redirect', $permalink, 301 );
			}
			wp_safe_redirect( $permalink, 301 );
			exit;
		}
	}
}

AWXCT_Frontend::init();
