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

class LiveLang_Frontend {

    /** @var LiveLang_DB */
    protected $db;
    protected $buffering = false;
    protected $original_request_uri = '';
    
    // Global recursion prevention flag
    private static $in_translation_hook = false;

    // Cached languages list for frontend memoization
    protected $cached_languages = null;

    public function __construct( $db ) {
        $this->db = $db;

        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
        add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
        add_shortcode( 'livelang_language_switcher', array( $this, 'livelang_language_switcher' ) );

        add_filter( 'query_vars', array( $this, 'add_query_vars' ) );
        add_filter( 'request', array( $this, 'handle_language_request' ) );
        add_filter( 'redirect_canonical', array( $this, 'disable_canonical_redirect_for_lang' ), 10, 2 );
        add_action( 'init', array( $this, 'add_rewrite_tag' ) );
        add_action( 'init', array( $this, 'setup_language_uri' ), 0 );

        // Detect language from URL as early as possible (before locale filters run).
        // setup_language_uri runs at init priority 0, so we run detection at priority 1.
        add_action( 'init', array( $this, 'livelang_detect_language' ), 1 );

        // Apply locale filter early so WP loads the right .mo files.
        // Priority 1 ensures we run before most plugins/themes call get_locale().
        add_filter( 'locale',           array( $this, 'filter_wp_locale' ), 1 );
        add_filter( 'determine_locale', array( $this, 'filter_wp_locale' ), 1 );

        // Switch locale + load gettext hooks right after init (textdomains load at init/after_setup_theme).
        // We use priority 15 so all plugins have registered their textdomains first.
        add_action( 'init', array( $this, 'maybe_switch_locale_and_hooks' ), 15 );
        add_action( 'init', array( $this, 'maybe_init_frontend_gettext_hooks' ), 16 );

        add_action( 'template_redirect', array( $this, 'start_buffer' ), 1 );

        // Keep locale re-applied at wp and wp_head as a safety net in case
        // something resets it between init and rendering.
        add_action( 'wp',      array( $this, 'ensure_locale_persists' ), 1 );
        add_action( 'wp_head', array( $this, 'ensure_locale_persists' ), 1 );

        // Filters to persist language in URLs
        add_filter( 'home_url', array( $this, 'filter_url' ), 10, 1 );
        add_filter( 'page_link', array( $this, 'filter_url' ), 10, 1 );
        add_filter( 'post_link', array( $this, 'filter_url' ), 10, 1 );
        add_filter( 'post_type_link', array( $this, 'filter_url' ), 10, 1 );
        add_filter( 'term_link', array( $this, 'filter_url' ), 10, 1 );
        add_filter( 'wp_nav_menu_objects', array( $this, 'filter_menu_urls' ), 10, 1 );
        add_filter( 'nav_menu_link_attributes', array( $this, 'nav_menu_link_attributes' ), 10, 4 );
        add_filter( 'wp_get_nav_menu_items', array( $this, 'expand_language_menu' ), 20 );
        add_filter( 'walker_nav_menu_start_el', array( $this, 'livelang_menu_item_output' ), 15, 4 );
        add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_toggle' ), 10 );
        add_action( 'init', array( $this, 'handle_frontend_toggle' ) );
        add_action( 'wp_head', array( $this, 'admin_bar_css' ), 5 );
        add_action( 'admin_head', array( $this, 'admin_bar_css' ), 5 );
        
        // AJAX response translation
        if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
            add_action( 'wp_loaded', array( $this, 'start_ajax_buffer' ), 1 );
        }
        
        add_action( 'wp_footer', array( $this, 'render_floating_switcher' ) );

        // SEO: hreflang alternate links + og:locale for multilingual pages.
        add_action( 'wp_head', array( $this, 'render_seo_head_tags' ), 1 );

        // WooCommerce AJAX fragments & price/cart filters
        add_filter( 'woocommerce_add_to_cart_fragments', array( $this, 'translate_cart_fragments' ), 20 );
        add_filter( 'woocommerce_update_order_review_fragments', array( $this, 'translate_cart_fragments' ), 20 );
        add_filter( 'woocommerce_get_price_html', array( $this, 'translate_block_content' ), 20 );
        add_filter( 'woocommerce_cart_item_name', array( $this, 'translate_block_content' ), 20 );
        add_filter( 'woocommerce_attribute', array( $this, 'translate_block_content' ), 20 );

        // Gutenberg Block Editor & Full Site Editing (FSE) filters
        add_filter( 'render_block', array( $this, 'translate_block_content' ), 10, 2 );
        add_filter( 'widget_block_content', array( $this, 'translate_block_content' ), 10, 2 );

        // Page Builders (Elementor, Divi, Beaver Builder, Bricks) filters
        add_filter( 'elementor/widget/render_content', array( $this, 'translate_block_content' ), 10, 2 );
        add_filter( 'elementor/frontend/the_content', array( $this, 'translate_block_content' ), 10 );
        add_filter( 'et_builder_render_layout', array( $this, 'translate_block_content' ), 10 );
        add_filter( 'fl_builder_render_content', array( $this, 'translate_block_content' ), 10 );
        add_filter( 'bricks/frontend/render', array( $this, 'translate_block_content' ), 10 );
    }

    /**
     * Output SEO-friendly <link rel="alternate" hreflang="..."> tags for every
     * configured language plus an x-default fallback, and an og:locale meta
     * tag for the current language. This helps search engines correctly
     * discover and index each language version of a page.
     */
    public function render_seo_head_tags() {
        if ( is_admin() || $this->is_builder_context() ) {
            return;
        }

        $languages = $this->get_languages_for_frontend();
        if ( empty( $languages ) || ! is_array( $languages ) ) {
            return;
        }

        $default_lang = $this->get_default_language();
        $current_lang = $this->getCurrentLanguage();

        echo "\n<!-- LiveLang: SEO hreflang tags -->\n";

        foreach ( $languages as $lang ) {
            if ( empty( $lang['code'] ) ) {
                continue;
            }
            $url = $this->get_language_url( $lang['code'] );
            printf(
                '<link rel="alternate" hreflang="%s" href="%s" />' . "\n",
                esc_attr( $lang['code'] ),
                esc_url( $url )
            );
        }

        // x-default should point at the default-language version so search
        // engines know which version to show users whose language doesn't match.
        printf(
            '<link rel="alternate" hreflang="x-default" href="%s" />' . "\n",
            esc_url( $this->get_language_url( $default_lang ) )
        );

        $og_locale = $this->get_locale_for_language( $current_lang );
        if ( $og_locale ) {
            printf( '<meta property="og:locale" content="%s" />' . "\n", esc_attr( $og_locale ) );
        }
    }

    public function start_ajax_buffer() {
        if ( is_admin() && ! wp_doing_ajax() ) {
            return;
        }
        ob_start( array( $this, 'buffer_callback' ) );
    }

    function add_rewrite_tag() {
        add_rewrite_tag( '%lang%', '([a-z]{2,5})' );
    }

    public function setup_language_uri() {
        if (!isset($_SERVER['REQUEST_URI'])) {
            return;
        }
        
        $raw_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
        $this->original_request_uri = $raw_uri;
        $request_uri = $raw_uri;
        $path = wp_parse_url($request_uri, PHP_URL_PATH);
        $home_path = wp_parse_url(get_option('home'), PHP_URL_PATH);
        
        if ($home_path && $home_path !== '/') {
            $path = preg_replace('#^' . preg_quote(rtrim($home_path, '/'), '#') . '#', '', $path);
        }
        
        if (empty($path) || substr($path, 0, 1) !== '/') {
            $path = '/' . $path;
        }

        if (preg_match('#^/([a-z]{2,5})(?:/(.*))?$#', $path, $matches)) {
            $lang_from_url = $matches[1];
            
            $valid_codes = array();
            $languages = $this->get_languages_for_frontend();
            if (is_array($languages)) {
                foreach ($languages as $l) {
                    if (isset($l['code'])) {
                        $valid_codes[] = $l['code'];
                    }
                }
            }
            
            if (in_array($lang_from_url, $valid_codes)) {
                $_GET['lang'] = $lang_from_url;
                $rest_of_path = isset($matches[2]) ? '/' . ltrim($matches[2], '/') : '/';
                
                $query = wp_parse_url($request_uri, PHP_URL_QUERY);
                $new_uri = $home_path && $home_path !== '/' ? rtrim($home_path, '/') . $rest_of_path : $rest_of_path;
                if ($query) {
                    $new_uri .= '?' . $query;
                }
                
                $_SERVER['REQUEST_URI'] = $new_uri;
            }
        }
    }

    function add_query_vars( $vars ) {
        $vars[] = 'lang';
        return $vars;
    }

    function handle_language_request( $query_vars ) {
        // Since we extract the language early in setup_language_uri,
        // WordPress natively parses the query_vars. We just need to persist the lang.
        if (isset($_GET['lang'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            $query_vars['lang'] = sanitize_key( wp_unslash( $_GET['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        }

        // Handle Lang-only request (Homepage for a language)
        // If we only have 'lang' (and possibly 'paged'/'page'), it's the true homepage
        $core_vars = array_diff( array_keys( $query_vars ), array( 'lang', 'paged', 'page', 'preview' ) );
        
        if ( isset( $query_vars['lang'] ) && empty( $core_vars ) ) {
            if ( 'page' === get_option( 'show_on_front' ) ) {
                $page_id = get_option( 'page_on_front' );
                if ( $page_id ) {
                    $query_vars['page_id'] = $page_id;
                    $query_vars['is_front_page'] = 1;
                }
            }
        }

        return $query_vars;
    }

    function disable_canonical_redirect_for_lang( $redirect_url, $requested_url ) {
        // If we are actively parsing a custom language, disable native WordPress
        // canonical redirect fixing, because we altered $_SERVER['REQUEST_URI'] 
        // to spoof native routing, which causes an infinite redirect loop.
        if ( isset($_GET['lang']) && sanitize_key( wp_unslash( $_GET['lang'] ) ) !== $this->get_default_language() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            return false;
        }
        return $redirect_url;
    }


    function livelang_detect_language() {
        if ( defined( 'LIVELANG_CURRENT_LANG' ) ) {
            return;
        }

        $lang = '';
        
        // Priority 1: $_GET['lang'] (set by our setup_language_uri or manual query string)
        if ( isset( $_GET['lang'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
             $lang = sanitize_key( wp_unslash( $_GET['lang'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        } 
        // Priority 2: WP Query Var (only if WP_Query is fully initialized and AFTER init)
        elseif ( did_action('init') && isset( $GLOBALS['wp_query'] ) && $GLOBALS['wp_query'] instanceof WP_Query ) {
             $lang = get_query_var( 'lang' );
        }

        // Priority 3: Early detection from URL (if called before 'init' or 'wp_query' is ready)
        if ( empty( $lang ) && ! is_admin() && isset( $_SERVER['REQUEST_URI'] ) ) {
            $req_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
            $path = wp_parse_url( $req_uri, PHP_URL_PATH );
            $home_path = wp_parse_url( get_option( 'home' ), PHP_URL_PATH );
            
            if ( $home_path && $home_path !== '/' ) {
                $path = preg_replace( '#^' . preg_quote( rtrim( $home_path, '/' ), '#' ) . '#', '', $path );
            }
            
            if ( preg_match( '#^/([a-z]{2,5})(?:/|$)#', $path, $matches ) ) {
                $potential_lang = $matches[1];
                $languages = $this->get_languages_for_frontend();
                if ( is_array( $languages ) ) {
                    foreach ( $languages as $l ) {
                        if ( isset( $l['code'] ) && $l['code'] === $potential_lang ) {
                            $lang = $potential_lang;
                            break;
                        }
                    }
                }
            }
        }

        if ( $lang ) {
            define( 'LIVELANG_CURRENT_LANG', $lang );
            if ( ! headers_sent() ) {
                setcookie( 'livelang_lang', $lang, array(
                    'expires'  => time() + MONTH_IN_SECONDS,
                    'path'     => COOKIEPATH,
                    'domain'   => COOKIE_DOMAIN,
                    'secure'   => is_ssl(),
                    'httponly' => false,
                    'samesite' => 'Lax',
                ) );
            }
        } else {
            // For normal page loads, if no prefix is present, it MUST be the default language.
            // We ignore the cookie here because the URL is explicitly NOT prefixed.
            $default_lang = $this->get_default_language();
            
            // Special case for AJAX: we might trust the cookie if no query var is present
            if ( defined('DOING_AJAX') && DOING_AJAX ) {
                $lang_from_cookie = isset( $_COOKIE['livelang_lang'] ) ? sanitize_key( $_COOKIE['livelang_lang'] ) : '';
                if ( $lang_from_cookie && preg_match( '/^[a-z]{2,5}$/', $lang_from_cookie ) ) {
                    define( 'LIVELANG_CURRENT_LANG', $lang_from_cookie );
                    return;
                }
            }

            define( 'LIVELANG_CURRENT_LANG', $default_lang );
            
            // Only update cookie if it's different
            if ( ! headers_sent() && ( !isset($_COOKIE['livelang_lang']) || $_COOKIE['livelang_lang'] !== $default_lang ) ) {
                setcookie( 'livelang_lang', $default_lang, array(
                    'expires'  => time() + MONTH_IN_SECONDS,
                    'path'     => COOKIEPATH,
                    'domain'   => COOKIE_DOMAIN,
                    'secure'   => is_ssl(),
                    'httponly' => false,
                    'samesite' => 'Lax',
                ) );
            }
        }
    }
   
    public function register_rest_routes() {
        register_rest_route( 'livelang-api/v1', '/save', array(
            'methods'             => 'POST',
            'callback'            => array( $this, 'rest_save_translation' ),
            'permission_callback' => array( $this, 'rest_permission_check' ),
            'args'                => array(
                'original'   => array(
                    'type'              => 'string',
                    'required'          => true,
                    'sanitize_callback' => function( $val ) { return wp_kses_post( is_array($val) ? '' : $val ); },
                ),
                'translated' => array(
                    'type'              => 'string',
                    'required'          => true,
                    'sanitize_callback' => function( $val ) { return wp_kses_post( is_array($val) ? '' : $val ); },
                ),
                'slug'       => array(
                    'type'              => 'string',
                    'required'          => false,
                    'sanitize_callback' => function( $val ) { return sanitize_text_field( is_array($val) ? '' : $val ); },
                ),
                'language'   => array(
                    'type'              => 'string',
                    'required'          => true,
                    'sanitize_callback' => function( $val ) { return sanitize_text_field( is_array($val) ? '' : $val ); },
                ),
                'is_global'  => array(
                    'type'              => 'integer',
                    'required'          => false,
                    'default'           => 0,
                    'sanitize_callback' => 'absint',
                ),
                'alignment'  => array(
                    'type'              => 'string',
                    'required'          => false,
                    'default'           => '',
                    'sanitize_callback' => function( $val ) { return sanitize_text_field( is_array($val) ? '' : $val ); },
                ),
            ),
        ) );

    }

    public function rest_permission_check() {
        return $this->is_user_allowed();
    }

    public function rest_save_translation( $request ) {
        $params = $request->get_params();
        
        // Support basic formatting for Gutenberg/modern builders
        $allowed_html = array(
            'b'      => array(),
            'strong' => array(),
            'i'      => array(),
            'em'     => array(),
            'a'      => array( 'href' => true, 'title' => true, 'target' => true ),
            'span'   => array( 'class' => true ),
            'br'     => array(),
            'u'      => array(),
        );

        $original   = isset( $params['original'] )   ? wp_kses( is_array( $params['original'] )   ? '' : $params['original'],   $allowed_html ) : '';
        $translated = isset( $params['translated'] ) ? wp_kses( is_array( $params['translated'] ) ? '' : $params['translated'], $allowed_html ) : '';
        $slug       = isset( $params['slug'] )       ? sanitize_text_field( is_array( $params['slug'] )       ? '' : $params['slug'] )       : '';
        $language   = isset( $params['language'] )   ? sanitize_text_field( is_array( $params['language'] )   ? '' : $params['language'] )   : '';
        $is_global  = isset( $params['is_global'] )  ? (int) ( is_array( $params['is_global'] )  ? 0 : $params['is_global'] )  : 0;
        $alignment  = isset( $params['alignment'] )  ? sanitize_text_field( is_array( $params['alignment'] )  ? '' : $params['alignment'] )  : '';

        if ( $original === '' || $translated === '' ) {
            return new WP_Error( 'empty_text', 'Empty text', array( 'status' => 400 ) );
        }

        // Validate language
        $languages = $this->get_languages_for_frontend();
        $is_valid  = false;
        foreach ( $languages as $lang ) {
            if ( $lang['code'] === $language ) {
                $is_valid = true;
                break;
            }
        }

        if ( ! $is_valid ) {
            return new WP_Error( 'invalid_language', 'Unsupported language code', array( 'status' => 400 ) );
        }

        // update if exists
        $existing = $this->db->get_translation_by_original_and_slug( $original, $slug, $language );
        if ( $existing ) {
            $this->db->update(
                $existing->id,
                array(
                    'translated_text' => $translated,
                    'is_global'       => $is_global,
                    'alignment'       => $alignment,
                )
            );
        } else {
            $this->db->insert(
                array(
                    'original_text'   => $original,
                    'translated_text' => $translated,
                    'slug'            => $slug,
                    'language'        => $language,
                    'is_global'       => $is_global,
                    'alignment'       => $alignment,
                )
            );
        }

        // Clear object cache explicitly so the new translation appears immediately
        $this->db->clear_translation_cache();

        return new WP_REST_Response( array( 'success' => true ), 200 );
    }



    public function handle_frontend_toggle() {
        if ( isset( $_GET['livelang_toggle'] ) && $this->is_user_allowed() ) {
            $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
            if ( ! $nonce || ! wp_verify_nonce( $nonce, 'livelang_toggle_action' ) ) {
                wp_die( esc_html__( 'Security check failed.', 'livelang' ) );
            }
            $state = sanitize_text_field( wp_unslash( $_GET['livelang_toggle'] ) );
            if ( in_array( $state, array( 'on', 'off' ) ) ) {
                setcookie( 'livelang_frontend_editor', $state, array(
                    'expires'  => time() + YEAR_IN_SECONDS,
                    'path'     => COOKIEPATH,
                    'domain'   => COOKIE_DOMAIN,
                    'secure'   => is_ssl(),
                    'httponly' => false,
                    'samesite' => 'Lax',
                ) );
                
                // Use stored original URI to preserve language prefix during redirect
                $server_req_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
                $redirect_url = !empty($this->original_request_uri) ? $this->original_request_uri : $server_req_uri;
                $redirect_url = remove_query_arg( array( 'livelang_toggle', '_wpnonce' ), $redirect_url );
                wp_safe_redirect( $redirect_url );
                exit;
            }
        }
    }

    public function add_admin_bar_toggle( $wp_admin_bar ) {
        // Only show on frontend to reduce backend clutter
        if ( is_admin() || ! is_user_logged_in() || ! $this->is_user_allowed() ) {
            return;
        }

        $user_pref = isset( $_COOKIE['livelang_frontend_editor'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['livelang_frontend_editor'] ) ) : 'off';
        $is_on = ( $user_pref === 'on' );
        $current_lang = $this->getCurrentLanguage();

        $title = sprintf(
            '<div class="livelang-toolbar-item">
                <img src="%s" class="livelang-flag-icon">
                <span class="livelang-brand">LiveLang</span>
                <div class="livelang-toggle-ui %s">
                    <span class="livelang-status-label">%s</span>
                    <div class="livelang-toggle-track">
                        <div class="livelang-toggle-handle"></div>
                    </div>
                </div>
            </div>',
            esc_url( $this->get_flag_url( $current_lang ) ),
            $is_on ? 'is-on' : 'is-off',
            $is_on ? __( 'Active', 'livelang' ) : __( 'Inactive', 'livelang' )
        );

        $wp_admin_bar->add_node( array(
            'id'    => 'livelang-toggle-node',
            'parent' => 'top-secondary',
            'title' => $title,
            'href'  => wp_nonce_url( add_query_arg( 'livelang_toggle', $is_on ? 'off' : 'on', $this->get_language_url( $current_lang ) ), 'livelang_toggle_action' ),
            'meta'  => array(
                'title' => __( 'Toggle LiveLang Editor', 'livelang' ),
            ),
        ) );

        // Language sub-nodes
        $languages = $this->get_languages_for_frontend();
        foreach ( $languages as $lang ) {
            $is_active = ( $lang['code'] === $current_lang );
            $wp_admin_bar->add_node( array(
                'id'     => 'livelang-lang-' . $lang['code'],
                'parent' => 'livelang-toggle-node',
                'title'  => sprintf(
                    '<div class="livelang-sub-lang %s">
                        <img src="%s" style="width:18px !important;height:12px !important;margin-right:8px !important;vertical-align:middle !important;border-radius:2px !important;object-fit:cover !important;"> %s
                        %s
                    </div>',
                    $is_active ? 'is-active' : '',
                    esc_url( $this->get_flag_url( $lang['code'] ) ),
                    esc_html( $lang['label'] ),
                    $is_active ? '<span class="livelang-check">✓</span>' : ''
                ),
                'href'   => $this->get_language_url( $lang['code'] ),
            ) );
        }
    }

    protected function get_language_url( $lang_code ) {
        $home_url = get_option('home');
        $default_lang = $this->get_default_language();

        // Get current path from REQUEST_URI for maximum reliability across different environments
        $req_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '/';
        $current_path = wp_parse_url( $req_uri, PHP_URL_PATH );
        
        // Strip home path if site is installed in a subdirectory
        $home_path = wp_parse_url( $home_url, PHP_URL_PATH );
        if ( $home_path && $home_path !== '/' ) {
            if ( strpos( $current_path, $home_path ) === 0 ) {
                $current_path = substr( $current_path, strlen( $home_path ) );
            }
        }
        
        // Ensure we have a clean path starting with a slash
        if ( ! $current_path || $current_path[0] !== '/' ) {
            $current_path = '/' . $current_path;
        }

        // Clean any existing language prefix from the current path
        $languages = $this->get_languages_for_frontend();
        if ( is_array( $languages ) ) {
            foreach ( $languages as $l ) {
                $prefix = '/' . $l['code'] . '/';
                if ( strpos( $current_path, $prefix ) === 0 ) {
                    $current_path = '/' . substr( $current_path, strlen( $prefix ) );
                    break;
                } elseif ( $current_path === '/' . $l['code'] || $current_path === '/' . $l['code'] . '/' ) {
                    $current_path = '/';
                    break;
                }
            }
        }

        // Prepare path and query for the final URL
        $current_path = ltrim( $current_path, '/' );
        $raw_query = isset( $_SERVER['QUERY_STRING'] ) ? sanitize_text_field( wp_unslash( $_SERVER['QUERY_STRING'] ) ) : '';
        $query_string = $raw_query !== '' ? '?' . $raw_query : '';

        if ( $lang_code === $default_lang ) {
            return trailingslashit( $home_url ) . $current_path . $query_string;
        } else {
            return trailingslashit( $home_url ) . $lang_code . '/' . $current_path . $query_string;
        }
    }

    public function admin_bar_css() {
        if ( ! is_user_logged_in() || ! $this->is_user_allowed() ) {
            return;
        }
        ?>
        <style>
        #wpadminbar #wp-admin-bar-livelang-toggle-node > .ab-item {
            display: flex !important;
            align-items: center !important;
            height: 32px !important;
            padding: 0 12px !important;
            background: transparent !important;
            transition: all 0.2s ease !important;
        }
        #wpadminbar #wp-admin-bar-livelang-toggle-node:hover > .ab-item {
            background: rgba(255, 255, 255, 0.1) !important;
        }
        .livelang-toolbar-item {
            display: flex;
            align-items: center;
            gap: 10px;
            color: #fff;
        }
        .livelang-flag-icon {
            width: 18px !important;
            height: auto !important;
            border-radius: 2px !important;
            box-shadow: 0 1px 2px rgba(0,0,0,0.2) !important;
        }
        .livelang-brand {
            font-weight: 700;
            font-size: 13px;
            opacity: 0.95;
            letter-spacing: 0.3px;
        }
        .livelang-toggle-ui {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 2px 6px;
            background: rgba(0, 0, 0, 0.25);
            border-radius: 14px;
            border: 1px solid rgba(255, 255, 255, 0.1);
        }
        .livelang-status-label {
            font-size: 10px;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 0.4px;
            min-width: 48px;
            text-align: center;
            transition: color 0.3s;
        }
        .is-on .livelang-status-label { color: #10b981; }
        .is-off .livelang-status-label { color: #94a3b8; }
        
        .livelang-toggle-track {
            position: relative;
            width: 28px;
            height: 14px;
            background: #475569;
            border-radius: 7px;
            transition: background 0.3s;
        }
        .is-on .livelang-toggle-track { background: #10b981; }
        
        .livelang-toggle-handle {
            position: absolute;
            top: 2px;
            left: 2px;
            width: 10px;
            height: 10px;
            background: #fff;
            border-radius: 50%;
            transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            box-shadow: 0 1px 2px rgba(0,0,0,0.3);
        }
        .is-on .livelang-toggle-handle {
            transform: translateX(14px);
        }
        
        .livelang-sub-lang {
            display: flex;
            align-items: center;
            padding: 4px 0;
        }
        .livelang-sub-lang.is-active {
            font-weight: 700;
            color: #10b981 !important;
        }
        .livelang-check {
            margin-left: auto;
            color: #10b981;
            padding-left: 10px;
        }
        #wp-admin-bar-livelang-toggle-node .ab-sub-wrapper {
            right: 0 !important;
            left: auto !important;
            border: none !important;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
        }
        @media screen and (max-width: 782px) {
            .livelang-brand { display: none; }
        }
        </style>
        <?php
    }

    protected function is_user_allowed() {
        if ( current_user_can( 'manage_options' ) ) {
            return true;
        }

        $settings = get_option( 'livelang_settings', array() );
        $is_enabled = !isset($settings['enabled']) || $settings['enabled'];
        
        if ( ! $is_enabled ) {
            return false;
        }

        if ( ! empty( $settings['allowed_roles'] ) && is_array( $settings['allowed_roles'] ) ) {
            $user    = wp_get_current_user();
            $roles   = (array) $user->roles;
            $allowed = $settings['allowed_roles'];
            if( in_array( 'all', $allowed, true ) ) {
                return true;
            }
            foreach ( $roles as $role ) {
                if ( in_array( $role, $allowed, true ) ) {
                    return true;
                }
            }
        }

        return false;
    }

    protected function is_enabled_for_user() {
        // First check if the user has manually turned it off via the admin bar toggle
        $user_pref = isset( $_COOKIE['livelang_frontend_editor'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['livelang_frontend_editor'] ) ) : 'off';
        if ( $user_pref === 'off' ) {
            return false;
        }

        if ( $this->is_builder_context() ) {
            return false;
        }

        return $this->is_user_allowed();
    }

    protected function is_builder_context() {
        if ( is_admin() || is_customize_preview() ) {
            return true;
        }

        // Broad Page Builder Detection
        $builder_params = array( 
            'elementor-preview', 'fl_builder', 'et_fb', 'brizy-edit', 'brizy-preview', 
            'ct_builder', 'vcv-be-editor', 'td_action', 'astra-preview', 'astra_hf_preview' 
        );
        foreach ( $builder_params as $param ) {
            if ( isset( $_GET[ $param ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
                return true;
            }
        }

        // Gutenberg / FSE
        if ( isset( $_GET['wp_site_mode'] ) || ( isset( $_GET['post_type'] ) && sanitize_key( wp_unslash( $_GET['post_type'] ) ) === 'wp_block' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            return true;
        }

        // Check for Gutenberg iframe
        if ( isset( $_GET['block-editor-iframe'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            return true;
        }

        return false;
    }

    public function start_buffer() {
        if ( is_admin() || $this->is_builder_context() ) {
            return;
        }

        // Language detection and locale switching already happened at init
        // (via maybe_switch_locale_and_hooks). We call detect here as a
        // safety net for edge cases where the constant was not yet defined.
        $this->livelang_detect_language();

        // Start output buffer so buffer_callback() can do string-level
        // replacement of custom translations stored in the database.
        ob_start( array( $this, 'buffer_callback' ) );
    }

    /**
     * Called at init priority 15.
     * Switches the WordPress locale so that textdomains loaded AFTER this point
     * (e.g. by themes via load_theme_textdomain at after_setup_theme, or plugins
     * via load_plugin_textdomain at init) use the correct .mo file.
     * Also registers gettext filters so that strings already loaded in memory
     * are intercepted and replaced with our database translations.
     */
    public function maybe_switch_locale_and_hooks() {
        // Never touch locale or gettext on admin pages, REST, or AJAX.
        if ( is_admin() && ! wp_doing_ajax() ) {
            return;
        }
        if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
            return;
        }
        if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
            return;
        }
        if ( ! wp_doing_ajax() && $this->is_builder_context() ) {
            return;
        }

        $language = $this->getCurrentLanguage();
        if ( empty( $language ) || $language === $this->get_default_language() ) {
            return;
        }

        // 1. Switch WordPress locale so subsequent load_textdomain() calls pick
        //    up the right .mo file.
        $this->set_current_locale();

        // 2. Register gettext filters so strings already in memory (loaded before
        //    this hook) are overridden with our database translations.
        $this->init_gettext_hooks();
    }

    /**
     * Register gettext hooks for frontend text replacement even when no locale
     * switch is needed. This prevents visible flashes on WooCommerce checkout
     * strings that are printed through esc_html_e() before the JS fallback runs.
     */
    public function maybe_init_frontend_gettext_hooks() {
        if ( is_admin() && ! wp_doing_ajax() ) {
            return;
        }
        if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
            return;
        }
        if ( ! wp_doing_ajax() && $this->is_builder_context() ) {
            return;
        }

        $this->init_gettext_hooks();
    }

    /**
     * Register gettext/translation filters for frontend page content.
     * Called only after locale is switched and only on non-admin/non-AJAX pages.
     */
    public function init_gettext_hooks() {
        // Guard: only run on frontend and only once.
        if ( self::$in_translation_hook ) {
            return;
        }
        if ( is_admin() && ! wp_doing_ajax() ) {
            return;
        }
        if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
            return;
        }

        static $hooks_registered = false;
        if ( $hooks_registered ) {
            return;
        }

        // These three filters intercept every __()/e__()/etc. call on the page.
        // The recursion guard (self::$in_translation_hook) in each callback
        // prevents infinite loops.
        add_filter( 'gettext',          array( $this, 'translate_gettext' ),         10, 3 );
        add_filter( 'ngettext',         array( $this, 'translate_ngettext' ),         10, 5 );
        add_filter( 'gettext_with_context', array( $this, 'translate_gettext_context' ), 10, 4 );
        $hooks_registered = true;
    }

    public function filter_wp_locale( $locale ) {
        // Skip real REST / AJAX requests – locale changes there are never needed.
        if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
            return $locale;
        }
        if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
            return $locale;
        }
        // Skip genuine wp-admin pages but not frontend requests that happen to
        // call is_admin() before the query is set up (it can return true then).
        if ( is_admin() && ! wp_doing_ajax() ) {
            return $locale;
        }

        // Priority 1: language already detected from the URL by setup_language_uri / livelang_detect_language.
        if ( defined( 'LIVELANG_CURRENT_LANG' ) && LIVELANG_CURRENT_LANG ) {
            $lang = LIVELANG_CURRENT_LANG;
        } else {
            // Priority 2: read from the *original* REQUEST_URI (before we rewrote it).
            // This is the only reliable source this early in the boot process.
            $raw_req_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
            $original_uri = ! empty( $this->original_request_uri )
                ? $this->original_request_uri
                : $raw_req_uri;

            $path = wp_parse_url( $original_uri, PHP_URL_PATH );
            if ( ! $path ) {
                $path = '/';
            }

            $home_path = wp_parse_url( get_option( 'home' ), PHP_URL_PATH );
            if ( $home_path && $home_path !== '/' ) {
                if ( strpos( $path, $home_path ) === 0 ) {
                    $path = substr( $path, strlen( $home_path ) );
                }
            }
            if ( ! $path || $path[0] !== '/' ) {
                $path = '/' . $path;
            }

            if ( preg_match( '#^/([a-z]{2,5})(?:/|$)#', $path, $matches ) ) {
                $candidate = $matches[1];
                // Validate against configured languages so we do not accidentally
                // treat a real URL segment (e.g. /shop/) as a language code.
                $valid = false;
                foreach ( $this->get_languages_for_frontend() as $l ) {
                    if ( isset( $l['code'] ) && $l['code'] === $candidate ) {
                        $valid = true;
                        break;
                    }
                }
                if ( ! $valid ) {
                    return $locale;
                }
                $lang = $candidate;
            } else {
                // No language prefix in the URL → this is the default language.
                return $locale;
            }
        }

        if ( empty( $lang ) || $lang === $this->get_default_language() ) {
            return $locale;
        }

        return $this->get_locale_for_language( $lang );
    }

    /**
     * Map language code to WordPress locale
     * e.g., 'bn' => 'bn_BD', 'es' => 'es_ES'
     */
    protected function get_locale_for_language($lang) {
        $locale_map = apply_filters('livelang_locale_map', array(
            'af' => 'af',
            'sq' => 'sq_AL',
            'am' => 'am_ET',
            'ar' => 'ar',
            'hy' => 'hy',
            'az' => 'az',
            'eu' => 'eu_ES',
            'be' => 'be_BY',
            'bn' => 'bn_BD',
            'bs' => 'bs_BA',
            'bg' => 'bg_BG',
            'ca' => 'ca_ES',
            // Prefer Traditional Chinese by default when admin shows zh_TW
            'zh' => 'zh_TW',
            // Assamese (present in WP admin language list)
            'as' => 'as',
            'hr' => 'hr_HR',
            'cs' => 'cs_CZ',
            'da' => 'da_DK',
            'nl' => 'nl_NL',
            'en' => 'en_US',
            'et' => 'et_EE',
            'fi' => 'fi_FI',
            'fr' => 'fr_FR',
            'gl' => 'gl_ES',
            'ka' => 'ka_GE',
            'de' => 'de_DE',
            'el' => 'el_GR',
            'gu' => 'gu_IN',
            'he' => 'he_IL',
            'hi' => 'hi_IN',
            'hu' => 'hu_HU',
            'is' => 'is_IS',
            'id' => 'id_ID',
            'it' => 'it_IT',
            'ja' => 'ja',
            'kn' => 'kn_IN',
            'kk' => 'kk_KZ',
            'km' => 'km',
            'ko' => 'ko_KR',
            'lo' => 'lo',
            'lv' => 'lv_LV',
            'lt' => 'lt_LT',
            'mk' => 'mk_MK',
            'ms' => 'ms_MY',
            'ml' => 'ml_IN',
            'mt' => 'mt_MT',
            'mr' => 'mr_IN',
            'mn' => 'mn',
            'ne' => 'ne_NP',
            'nb' => 'nb_NO',
            'nn' => 'nn_NO',
            'fa' => 'fa_IR',
            'pl' => 'pl_PL',
            'pt' => 'pt_PT',
            'pa' => 'pa_IN',
            'ro' => 'ro_RO',
            'ru' => 'ru_RU',
            'sr' => 'sr_RS',
            'si' => 'si_LK',
            'sk' => 'sk_SK',
            'sl' => 'sl_SI',
            'es' => 'es_ES',
            'sw' => 'sw_KE',
            'sv' => 'sv_SE',
            'ta' => 'ta_IN',
            'te' => 'te_IN',
            'th' => 'th',
            'tr' => 'tr_TR',
            'uk' => 'uk_UA',
            'ur' => 'ur_PK',
            'uz' => 'uz_UZ',
            'vi' => 'vi_VN',
            'cy' => 'cy_GB',
        ));

        return isset($locale_map[$lang]) ? $locale_map[$lang] : $lang;
    }

    /**
     * Switch the WordPress locale to match the current language and reload
     * any textdomains that were already loaded under the old locale.
     *
     * This must be called early enough that most textdomains have NOT yet been
     * loaded (i.e. at init priority ~15, before after_setup_theme fires for
     * themes or before plugins call load_plugin_textdomain).  When called late
     * it also forces a reload of the default and active plugin/theme textdomains
     * so that already-in-memory translations are replaced.
     */
    protected function set_current_locale() {
        $language = $this->getCurrentLanguage();
        if ( empty( $language ) || $language === $this->get_default_language() ) {
            return;
        }

        $locale = $this->get_locale_for_language( $language );

        // Switch WordPress locale.  switch_to_locale() pushes onto a stack so
        // subsequent restore_previous_locale() calls by other plugins won't
        // permanently undo our switch.
        if ( function_exists( 'switch_to_locale' ) ) {
            switch_to_locale( $locale );
        } else {
            // Fallback for very old WP versions.
            global $wp_locale;
            $GLOBALS['wp_locale'] = new WP_Locale();
        }

        // Force-reload the WordPress core default textdomain so that any
        // strings already loaded under the old locale are replaced.
        $mo_file = WP_LANG_DIR . '/' . $locale . '.mo';
        if ( file_exists( $mo_file ) ) {
            load_textdomain( 'default', $mo_file );
        }

        // Reload active theme textdomain if it was already loaded.
        // get_template() returns the parent theme slug which is also the textdomain.
        $theme = wp_get_theme();
        if ( $theme->exists() ) {
            $theme_domain = $theme->get( 'TextDomain' );
            if ( $theme_domain ) {
                $theme_mo = get_template_directory() . '/languages/' . $locale . '.mo';
                if ( file_exists( $theme_mo ) ) {
                    load_textdomain( $theme_domain, $theme_mo );
                }
                // Also check the WP_LANG_DIR themes sub-directory (recommended location).
                $theme_mo_lang = WP_LANG_DIR . '/themes/' . $theme_domain . '-' . $locale . '.mo';
                if ( file_exists( $theme_mo_lang ) ) {
                    load_textdomain( $theme_domain, $theme_mo_lang );
                }
            }
        }

        // Optionally set the PHP/POSIX locale for date/number formatting.
        // This can silently fail if the OS locale is not installed – that is OK.
        $country      = $this->livelang_language_to_country( $language );
        $posix_locale = $language . '_' . strtoupper( $country );
        $locales_to_try = array(
            $locale . '.UTF-8',
            $locale . '.utf8',
            $locale,
            $posix_locale . '.UTF-8',
            $posix_locale . '.utf8',
            $posix_locale,
        );
        @setlocale( LC_ALL, ...$locales_to_try ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
    }


    /**
     * Ensure the locale has not been reset by a theme or plugin.
     * Called at the 'wp' and 'wp_head' hooks as a safety net.
     */
    public function ensure_locale_persists() {
        if ( is_admin() && ! wp_doing_ajax() ) {
            return;
        }

        $language = $this->getCurrentLanguage();
        if ( empty( $language ) || $language === $this->get_default_language() ) {
            return;
        }

        $locale         = $this->get_locale_for_language( $language );
        $current_locale = get_locale();

        if ( $current_locale !== $locale ) {
            // Re-apply locale and reload textdomains so the corrected strings
            // are available for the rest of the page render.
            $this->set_current_locale();
        }
    }


    protected function is_rtl_language($lang) {
        $rtl_langs = array('ar', 'he', 'fa', 'ur', 'ps', 'yi', 'syr', 'dv', 'ku', 'ug');
        return in_array(strtolower(substr($lang, 0, 2)), $rtl_langs, true);
    }

    protected function apply_rtl_formatting( &$content, $language ) {
        if ( $this->is_rtl_language( $language ) ) {
            // Force HTML dir attributes
            if (preg_match('/<html[^>]*dir=["\']ltr[\'"][^>]*>/i', $content)) {
                 $content = preg_replace('/(<html[^>]*dir=["\'])ltr([\'"][^>]*>)/i', '${1}rtl${2}', $content);
            } else if (strpos($content, 'dir="rtl"') === false && preg_match('/<html[^>]*>/i', $content)) {
                 $content = preg_replace('/(<html[^>]*)>/i', '$1 dir="rtl">', $content, 1);
            }

            // Force body classes for themes that rely on it
            if ( preg_match( '/<body[^>]*class=["\'][^"\']*["\'][^>]*>/i', $content ) ) {
                 $content = preg_replace( '/(<body[^>]*class=["\'])([^"\']*)(["\'][^>]*>)/i', '${1}rtl $2${3}', $content, 1 );
            } else if ( preg_match( '/<body[^>]*>/i', $content ) ) {
                 $content = preg_replace( '/(<body[^>]*)>/i', '$1 class="rtl">', $content, 1 );
            }
        }
    }

    public function buffer_callback( $content ) {
        if ( empty($content) ) {
            return $content;
        }

        // Early exit for REST API and WordPress/WooCommerce AJAX to avoid corrupting JSON
        if ( (defined('REST_REQUEST') && REST_REQUEST) || (defined('DOING_AJAX') && DOING_AJAX) ) {
            // Detect JSON
            $trimmed = ltrim($content);
            if ( isset($trimmed[0]) && ($trimmed[0] === '{' || $trimmed[0] === '[') ) {
                return $content;
            }
        }

        $slug     = $this->get_current_slug();
        $language = $this->getCurrentLanguage();

        // Apply translations regardless of default status if we are not in admin
        if ( is_admin() ) {
            return $content;
        }

        $map = $this->get_translations_map($slug, $language);

        $this->apply_rtl_formatting( $content, $language );

        if (empty($map)) {
            return $content;
        }

        return $this->translate_html_string( $content, $map );
    }

    /**
     * Translate a raw HTML string against a pre-built original => translated
     * map. This is the engine buffer_callback() uses for full-page HTML, and
     * is also reused for WooCommerce AJAX fragments (mini-cart, checkout
     * review) so cart/checkout updates stay translated without duplicating
     * the tag-splitting/skip-block logic in two places.
     */
    protected function translate_html_string( $html, array $map ) {
        if ( empty( $html ) || ! is_string( $html ) || empty( $map ) ) {
            return $html;
        }

        $regex_map = array();
        $strtr_map_attr = array();
        $strtr_map_text = array();
        $strtr_map_html = array();

        foreach ($map as $original => $data) {
            $translated = is_array($data) ? $data['translated'] : $data;
            $alignment = is_array($data) ? ($data['alignment'] ?? null) : null;
            
            if (empty($original) || empty($translated) || !is_string($original) || !is_string($translated)) {
                continue;
            }

            $text_translated = $translated;
            if ( ! empty( $alignment ) ) {
                // Use a harmless inline span and CSS :has() to style the parent element instantly.
                // This prevents FOUC and avoids layout-breaking block wrappers.
                $text_translated = '<span class="ll-align-' . esc_attr($alignment) . '-target">' . $translated . '</span>';
            }

            if (strpos($original, '::NUM::') !== false) {
                // Regex map doesn't easily support distinct attr vs text maps cleanly without heavy rewrite,
                // so we just pass the raw translated for both for now or handle it.
                $regex_map[$original] = $translated;
            } else if (strpos($original, '<') !== false && strpos($original, '>') !== false) {
                $strtr_map_html[$original] = $text_translated;
            } else {
                $strtr_map_attr[$original] = $translated;
                $strtr_map_text[$original] = $text_translated;

                // Media & Image URL variation mapping: Generate protocol, root-relative, relative, and thumbnail size variations
                if ( preg_match( '/\.(png|jpe?g|webp|gif|svg|avif|bmp|ico)(\?.*)?$/i', $original ) || strpos( $original, '/uploads/' ) !== false ) {
                    $base_orig = preg_replace( '/\?.*$/', '', $original );
                    $url_variants = array( $original, $base_orig );

                    // Protocol variations (http <-> https)
                    if ( strpos( $original, 'http://' ) === 0 ) {
                        $url_variants[] = str_replace( 'http://', 'https://', $original );
                    } elseif ( strpos( $original, 'https://' ) === 0 ) {
                        $url_variants[] = str_replace( 'https://', 'http://', $original );
                    }

                    // Root-relative and relative variations (/wp-content/... and wp-content/...)
                    $parsed = wp_parse_url( $original );
                    if ( ! empty( $parsed['path'] ) ) {
                        $path = $parsed['path'];
                        $url_variants[] = $path;
                        $url_variants[] = ltrim( $path, '/' );
                    }

                    // Dimension variations (e.g. photo-1024x768.jpg -> photo.jpg)
                    foreach ( $url_variants as $uv ) {
                        if ( preg_match( '/^(.+)-[0-9]+x[0-9]+(\.[a-z0-9]+)$/i', $uv, $dm ) ) {
                            $unscaled = $dm[1] . $dm[2];
                            $url_variants[] = $unscaled;
                        }
                    }

                    foreach ( array_unique( $url_variants ) as $variant ) {
                        if ( ! empty( $variant ) ) {
                            $strtr_map_attr[ $variant ] = $translated;
                            $strtr_map_text[ $variant ] = $translated;
                        }
                    }
                }
            }
        }

        if (empty($strtr_map_attr) && empty($regex_map) && empty($strtr_map_html)) {
            return $html;
        }
        
        if (!empty($strtr_map_html)) {
            foreach ($strtr_map_html as $orig => $trans) {
                // Use ~ as delimiter so we don't have to escape /
                $pattern = preg_quote($orig, '~');
                // Replace escaped spaces with \s+ to tolerate any whitespace (newlines, tabs, multiple spaces)
                $pattern = str_replace(' ', '\s+', $pattern);
                
                // Tolerate variations of <br> tags in source code
                $pattern = str_replace('\<br\>', '\<br\s*/?\>', $pattern);
                $pattern = str_replace('\<br\s+\/\>', '\<br\s*/?\>', $pattern);

                // Escape $ for preg_replace
                $trans_escaped = str_replace('$', '\$', $trans);
                
                $html_new = preg_replace('~' . $pattern . '~iu', $trans_escaped, $html);
                if ($html_new !== null) {
                    $html = $html_new;
                }
            }
        }

        // Split by tags to avoid replacing strings inside HTML tags/attributes
        // This prevents breaking CSS classes, IDs, and style/script content.
        // We use a fail-safe check for preg_split errors (like backtrack limits on large pages)
        $parts = preg_split('/(<[^>]+>)/u', $html, -1, PREG_SPLIT_DELIM_CAPTURE);

        if ( $parts === false ) {
            // Fallback to non-UTF8 split if the Unicode split fails
            $parts = preg_split('/(<[^>]+>)/', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
        }

        if ( $parts === false || empty($parts) ) {
            return $html; // Return original if splitting fails entirely
        }

        $output = '';

        $skip_translation_block = '';
        $in_title_block = false;

        foreach ($parts as $part) {
            if (empty($part)) continue;

            if (strpos($part, '<') === 0) {
                if (preg_match('#^<\s*title\b#i', $part)) {
                    $in_title_block = true;
                } elseif (preg_match('#^<\s*/\s*title\s*>#i', $part)) {
                    $in_title_block = false;
                }

                if ( $skip_translation_block && preg_match( '#^<\s*/\s*' . preg_quote( $skip_translation_block, '#' ) . '\s*>#i', $part ) ) {
                    $output .= $part;
                    $skip_translation_block = '';
                    continue;
                }

                if ( ! $skip_translation_block && preg_match( '#^<\s*(script|style|textarea|template|noscript)\b#i', $part, $block_match ) ) {
                    $skip_translation_block = strtolower( $block_match[1] );
                    $output .= $part;
                    continue;
                }

                // Handle img and source tags specifically to prevent responsive srcset and lazyload from overriding translated src
                if (preg_match('/^<\s*(img|source)\b/i', $part)) {
                    $translated_tag = strtr($part, $strtr_map_attr);
                    $tag_changed = ($translated_tag !== $part);

                    // If tag changed OR contains any translated image URL in src/srcset, strip responsive srcset/sizes
                    if ($tag_changed || preg_match('/\.(png|jpe?g|webp|gif|svg|avif)/i', $translated_tag)) {
                        // Strip original srcset, data-srcset, data-lazy-srcset, and sizes so browser & lazyload use translated src
                        $translated_tag = preg_replace('/\s+(srcset|data-srcset|data-lazy-srcset|sizes)=["\'][^"\']*["\']/i', '', $translated_tag);
                        // Sync data-src, data-lazy-src, data-orig-src with translated src
                        if (preg_match('/src=["\']([^"\']+)["\']/i', $translated_tag, $src_m)) {
                            $new_src = $src_m[1];
                            $translated_tag = preg_replace('/data-src=["\'][^"\']*["\']/i', 'data-src="' . esc_attr($new_src) . '"', $translated_tag);
                            $translated_tag = preg_replace('/data-lazy-src=["\'][^"\']*["\']/i', 'data-lazy-src="' . esc_attr($new_src) . '"', $translated_tag);
                            $translated_tag = preg_replace('/data-orig-src=["\'][^"\']*["\']/i', 'data-orig-src="' . esc_attr($new_src) . '"', $translated_tag);
                        }
                    }
                    $output .= $translated_tag;
                    continue;
                }

                // It's a tag. We selectively allow placeholders, titles, alt text,
                // images, lazyload attributes, inline style background-images, button values, and accessibility labels.
                if (preg_match('/ (placeholder|title|alt|src|srcset|data-src|data-lazy-src|data-orig-src|data-srcset|data-lazy-srcset|value|aria-label|data-title|style)=["\']/i', $part)) {
                   $output .= strtr($part, $strtr_map_attr);
                   continue;
                }

                // SEO: translate the "content" attribute of <meta> tags used for
                // description / Open Graph / Twitter Card so search engines and
                // social previews show the translated text, not the default language.
                if (
                    stripos($part, '<meta') === 0 &&
                    preg_match('/(?:name|property)=["\'](description|og:title|og:description|twitter:title|twitter:description)["\']/i', $part)
                ) {
                   $output .= strtr($part, $strtr_map_attr);
                   continue;
                }

                $output .= $part;
                continue;
            }

            if ( $skip_translation_block ) {
                $output .= $part;
                continue;
            }

            // It's text node content. Apply replacements.
            // Do not apply text-based HTML scripts inside <title> tags.
            $map_to_use = $in_title_block ? $strtr_map_attr : $strtr_map_text;
            $translated_part = $part;

            foreach ($map_to_use as $original => $translated) {
                if (empty($original) || empty($translated)) continue;
                // If text already contains the translated version, skip replacing original to avoid double translation
                if (strpos($translated_part, $translated) !== false) {
                    continue;
                }
                if (strpos($translated_part, $original) !== false) {
                    $pattern = '/' . preg_quote($original, '/') . '/u';
                    $translated_part = preg_replace($pattern, $translated, $translated_part);
                }
            }

            // 2. Regex-based numeric replacements
            foreach ($regex_map as $original => $translated) {
                if (strpos($translated_part, $translated) !== false) {
                    continue;
                }
                $pattern_parts = explode('::NUM::', $original);
                $escaped_parts = array_map(function($p) { return preg_quote($p, '/'); }, $pattern_parts);
                $regex_pattern = '/'.implode('([0-9]+)', $escaped_parts).'/u';

                $translated_part = preg_replace_callback($regex_pattern, function($matches) use ($translated) {
                    $repl = $translated;
                    for ($i = 1; $i < count($matches); $i++) {
                        $repl = preg_replace('/\{NUM\}/', $matches[$i], $repl, 1);
                    }
                    return $repl;
                }, $translated_part);
            }

            $output .= $translated_part;
        }

        return $output;
    }

    /**
     * Translate WooCommerce's legacy AJAX fragment responses (mini-cart,
     * checkout order review) so cart/checkout updates stay translated even
     * though they never pass through buffer_callback() (fragments are
     * returned as a JSON-encoded array via wp_send_json(), not full-page
     * HTML). Store API / Blocks-based cart updates don't need this — they
     * render via React on the client, where the dynamic MutationObserver in
     * livelang-frontend.js already picks up the resulting DOM text.
     */
    public function translate_cart_fragments( $fragments ) {
        if ( ! is_array( $fragments ) || empty( $fragments ) ) {
            return $fragments;
        }

        $language         = $this->getCurrentLanguage();
        $default_language = $this->get_default_language();

        if ( ! $language || $language === $default_language ) {
            return $fragments;
        }

        $slug = $this->get_current_slug();
        $map  = $this->get_translations_map( $slug, $language );

        if ( empty( $map ) ) {
            return $fragments;
        }

        foreach ( $fragments as $selector => $html ) {
            if ( is_string( $html ) ) {
                $fragments[ $selector ] = $this->translate_html_string( $html, $map );
            }
        }

        return $fragments;
    }

    /**
     * Translates Gutenberg Blocks, FSE Template Parts, and Block Patterns.
     */
    public function translate_block_content( $block_content, $block = null ) {
        if ( is_admin() && ! wp_doing_ajax() ) {
            return $block_content;
        }
        if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
            return $block_content;
        }

        $language = $this->getCurrentLanguage();
        $default_language = $this->get_default_language();

        if ( empty( $language ) || $language === $default_language || empty( $block_content ) || ! is_string( $block_content ) ) {
            return $block_content;
        }

        $slug = $this->get_current_slug();
        $map  = $this->get_translations_map( $slug, $language );

        if ( empty( $map ) ) {
            return $block_content;
        }

        return $this->translate_html_string( $block_content, $map );
    }

    public function get_translations_map($slug, $language = null) {
        if( !$language ) {
            $language = $this->getCurrentLanguage();
        }
        if( !$slug ) {
            $slug = $this->get_current_slug();
        }

        $settings = get_option( 'livelang_settings', array() );
        $translate_numbers = ! empty( $settings['translate_numbers'] ) ? true : false;
        
        $cache_version = (int) get_option( 'livelang_cache_version', 0 );
        $cache_key   = "livelang_translations_{$language}_{$slug}_{$cache_version}";
        $cache_group = 'livelang';

        $translations = wp_cache_get($cache_key, $cache_group);

        if (false === $translations) {
            $translations = $this->db->get_translations_for_slug($slug, $language);

            wp_cache_set(
                $cache_key,
                $translations,
                $cache_group,
                apply_filters('livelang_cache_ttl', 12 * HOUR_IN_SECONDS)
            );
        }

        // convert to map with safety checks
        $map = array();
        if (!empty($translations) && is_array($translations)) {
            foreach ($translations as $row) {
                // Type check: ensure $row is an object with the required properties
                if (!is_object($row) || empty($row->original_text) || empty($row->translated_text)) {
                    continue;
                }
                
                // Type check: ensure properties are strings
                $original = trim((string) $row->original_text);
                $translated = trim((string) $row->translated_text);

                // If numbers should be ignored for matching, create a
                // normalized map key where all digit runs are replaced by
                // the placeholder ::NUM::. Store the translated text with a
                // {NUM} placeholder so we can inject the actual number when
                // performing replacements.
                // Do NOT process URLs (images, uploads, media) as number-normalized strings
                $is_media_url = (bool) preg_match( '/\.(png|jpe?g|webp|gif|svg|avif|bmp|ico)(\?.*)?$/i', $original ) || ( strpos( $original, '/uploads/' ) !== false ) || ( strpos( $original, 'http://' ) === 0 ) || ( strpos( $original, 'https://' ) === 0 );

                // Handle dynamic number placeholders (::NUM:: and {NUM}) for regular text strings only
                if ( ! $is_media_url && ( strpos( $translated, '{NUM}' ) !== false || $this->text_contains_numbers( $original ) ) ) {
                    $orig_numbers_count  = preg_match_all( '/\p{N}+/u', $original );
                    $trans_numbers_count = preg_match_all( '/\p{N}+/u', $translated );

                    if ( $orig_numbers_count > 0 || strpos( $translated, '{NUM}' ) !== false ) {
                        $normalized_key              = preg_replace( '/\p{N}+/u', '::NUM::', $original );
                        $translated_with_placeholder = ( strpos( $translated, '{NUM}' ) !== false ) ? $translated : preg_replace( '/\p{N}+/u', '{NUM}', $translated );

                        if ( ! isset( $map[ $normalized_key ] ) ) {
                            $map[ $normalized_key ] = array( 'translated' => $translated_with_placeholder, 'alignment' => isset($row->alignment) ? $row->alignment : '' );
                        }
                        continue;
                    }
                }
                
                if (empty($original) || empty($translated)) {
                    continue;
                }
                
                $map[$original] = array( 'translated' => $translated, 'alignment' => isset($row->alignment) ? $row->alignment : '' );
            }
        }

        return $map;
    }

    /**
     * Check if text contains any numbers (digits)
     *
     * @param string $text The text to check
     * @return bool True if text contains numbers, false otherwise
     */
    protected function text_contains_numbers( $text ) {
        return (bool) preg_match( '/\d/', $text );
    }



    public function translate_gettext($translated, $text, $domain) {
        // Prevent recursion FIRST (before anything else)
        if (self::$in_translation_hook) {
            return $translated;
        }

        // Early exits prevent unnecessary processing
        if (empty($text)) {
            return $translated;
        }

        if ( ( is_admin() && ! wp_doing_ajax() ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
            return $translated;
        }

        // Set flag immediately
        self::$in_translation_hook = true;
        
        try {
            // Get language
            $language = $this->getCurrentLanguage();
            if (empty($language)) {
                return $translated;
            }

            // Get slug
            $slug = $this->get_current_slug();
            if (empty($slug)) {
                $slug = 'home';
            }

            $map = $this->get_translations_map($slug, $language);
            $trimmed_text = trim($text);
            if (isset($map[$trimmed_text]) && $map[$trimmed_text] !== '') {
                $data = $map[$trimmed_text];
                return is_array($data) ? $data['translated'] : $data;
            }
        } finally {
            self::$in_translation_hook = false;
        }

        return $translated;
    }

    public function translate_ngettext($translated, $single, $plural, $number, $domain) {
        // Prevent recursion FIRST
        if (self::$in_translation_hook) {
            return $translated;
        }

        // Early exits
        if (empty($single) && empty($plural)) {
            return $translated;
        }

        if ( ( is_admin() && ! wp_doing_ajax() ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
            return $translated;
        }

        // Set flag immediately
        self::$in_translation_hook = true;
        
        try {
            // Get language
            $language = $this->getCurrentLanguage();
            if (empty($language)) {
                return $translated;
            }

            // Determine which text to use
            $check_text = ($number == 1) ? $single : $plural;
            if (empty($check_text)) {
                return $translated;
            }

            // Get slug
            $slug = $this->get_current_slug();
            if (empty($slug)) {
                $slug = 'home';
            }

            $map = $this->get_translations_map($slug, $language);
            $trimmed_text = trim($check_text);
            if (isset($map[$trimmed_text]) && $map[$trimmed_text] !== '') {
                $data = $map[$trimmed_text];
                return is_array($data) ? $data['translated'] : $data;
            }
        } finally {
            self::$in_translation_hook = false;
        }

        return $translated;
    }

    public function translate_gettext_context($translated, $text, $context, $domain) {
        // Prevent recursion FIRST
        if (self::$in_translation_hook) {
            return $translated;
        }

        // Early exits
        if (empty($text) || empty($context)) {
            return $translated;
        }

        if ( ( is_admin() && ! wp_doing_ajax() ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
            return $translated;
        }

        // Set flag immediately
        self::$in_translation_hook = true;
        
        try {
            // Get language
            $language = $this->getCurrentLanguage();
            if (empty($language)) {
                return $translated;
            }

            // Get slug
            $slug = $this->get_current_slug();
            if (empty($slug)) {
                $slug = 'home';
            }

            // Build context key
            $lookup_key = $context . '|' . $text;

            $map = $this->get_translations_map($slug, $language);
            $trimmed_key = trim($lookup_key);
            if (isset($map[$trimmed_key]) && $map[$trimmed_key] !== '') {
                $data = $map[$trimmed_key];
                return is_array($data) ? $data['translated'] : $data;
            }
        } finally {
            self::$in_translation_hook = false;
        }

        return $translated;
    }


    protected function get_only_text_from_transation ($text) {
        return preg_replace('/\p{N}/u', '', $text);
    }

    protected function get_only_number_from_transation ($text) {
        return preg_replace('/[^\p{N}]/u', '', $text);
    }


    protected function get_current_slug() {
        global $wp;
        if (!isset($wp) || !isset($wp->request)) {
            if ( wp_doing_ajax() ) {
                $ajax_slug = $this->get_ajax_referer_slug();
                if ( $ajax_slug ) {
                    return $ajax_slug;
                }
            }
            return 'home';
        }
        
        // Get raw slug without calling functions that might trigger hooks
        $slug = trim($wp->request, '/');
        
        if (empty($slug)) {
            if ( wp_doing_ajax() ) {
                $ajax_slug = $this->get_ajax_referer_slug();
                if ( $ajax_slug ) {
                    return $ajax_slug;
                }
            }
            return 'home';
        }
        
        // Remove language prefix if present (simple string operation, no function calls)
        $language = $this->getCurrentLanguage();
        if (!empty($language) && $language !== $this->get_default_language()) {
            $prefix = $language . '/';
            if (strpos($slug, $prefix) === 0) {
                $slug = substr($slug, strlen($prefix));
            } elseif ($slug === $language) {
                $slug = '';
            }
        }
        
        return empty($slug) ? 'home' : $slug;
    }

    protected function get_ajax_referer_slug() {
        $referer = wp_get_referer();
        if ( ! $referer && ! empty( $_SERVER['HTTP_REFERER'] ) ) {
            $referer = sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ) );
        }

        if ( ! $referer ) {
            return '';
        }

        $path = wp_parse_url( $referer, PHP_URL_PATH );
        if ( ! $path ) {
            return 'home';
        }

        $home_path = wp_parse_url( get_option( 'home' ), PHP_URL_PATH );
        if ( $home_path && $home_path !== '/' && strpos( $path, $home_path ) === 0 ) {
            $path = substr( $path, strlen( $home_path ) );
        }

        $slug = trim( $path, '/' );
        if ( empty( $slug ) ) {
            return 'home';
        }

        $languages = $this->get_languages_for_frontend();
        foreach ( $languages as $language ) {
            if ( empty( $language['code'] ) ) {
                continue;
            }

            $prefix = $language['code'] . '/';
            if ( strpos( $slug, $prefix ) === 0 ) {
                $slug = substr( $slug, strlen( $prefix ) );
                break;
            }
            if ( $slug === $language['code'] ) {
                $slug = '';
                break;
            }
        }

        return empty( $slug ) ? 'home' : $slug;
    }

    protected function get_homepage_slug() {
        $homepage_id = (int) get_option( 'page_on_front' );
        if ( $homepage_id ) {
            $homepage_slug = get_post_field( 'post_name', $homepage_id );
            return $homepage_slug ? $homepage_slug : '';
        }
        return '';
    }

    /**
     * Get languages for frontend from database
     *
     * @return array
     */
    protected function get_languages_for_frontend() {
        if ( null !== $this->cached_languages ) {
            return $this->cached_languages;
        }

        $languages = get_option( 'livelang_languages', array() );
        
        if ( ! is_array( $languages ) ) {
            return array();
        }

        if ( empty( $languages ) ) {
            // Fallback to defaults
            $languages = array(
                array(
                    'code'      => 'en',
                    'label'     => 'English',
                    'is_default' => 1,
                    'order'     => 0,
                ),
            );
        }
        // Sort by order
        usort( $languages, function ( $a, $b ) {
            return $a['order'] - $b['order'];
        });

        $this->cached_languages = $languages;
        return $languages;
    }

    /**
     * Get default language
     *
     * @return string
     */
    protected function get_default_language() {
        $languages = $this->get_languages_for_frontend();
        foreach ( $languages as $lang ) {
            if ( ! empty( $lang['is_default'] ) ) {
                return $lang['code'];
            }
        }
        return 'en';
    }


    public function livelang_rest_url( $path ) {
        $base = site_url(); // safer than home_url
        return $base . '/?rest_route=/' . ltrim( $path, '/' );
    }

    public function enqueue_assets() {
        wp_enqueue_style(
            'livelang-frontend',
            LIVELANG_PLUGIN_URL . 'assets/css/livelang-frontend.css',
            array(),
            LIVELANG_VERSION
        );

        wp_enqueue_script(
            'livelang-frontend',
            LIVELANG_PLUGIN_URL . 'assets/js/livelang-frontend.js',
            array(),
            LIVELANG_VERSION,
            true
        );

        if ( $this->is_user_allowed() ) {
            wp_enqueue_media();
            wp_enqueue_script(
                'livelang-frontend-editor',
                LIVELANG_PLUGIN_URL . 'assets/js/livelang-frontend-editor.js',
                array('jquery'),
                LIVELANG_VERSION,
                true
            );
        }

        $slug         = $this->get_current_slug();
        $language     = $this->getCurrentLanguage();
        $default_lang = $this->get_default_language();
        $dict = array();
        $translations = $this->db->get_translations_for_slug( $slug, $language );
        if ( ! empty( $translations ) ) {
            foreach ( $translations as $row ) {
                $dict[] = array(
                    'original'   => $row->original_text,
                    'translated' => $row->translated_text,
                    'alignment'  => isset( $row->alignment ) ? $row->alignment : '',
                );
            }
        }

        $rest_url = $this->livelang_rest_url( 'livelang-api/v1' );
        if ( is_ssl() ) {
            $rest_url = set_url_scheme( $rest_url, 'https' );
        }

        // Get languages from database
        $languages_data = $this->get_languages_for_frontend();
        $languages = array();
        foreach ( $languages_data as $lang ) {
            $languages[ $lang['code'] ] = $lang['label'];
        }

        wp_localize_script(
            'livelang-frontend',
            'LiveLangSettings',
            array(
                'restUrl' => untrailingslashit( esc_url_raw( $rest_url ) ),
                'nonce'   => $this->is_user_allowed() ? wp_create_nonce( 'wp_rest' ) : '',
                'isAllowed' => $this->is_user_allowed(),
                'enabled' => $this->is_enabled_for_user(),
                'slug'    => $slug,
                'currentLanguage' => $this->getCurrentLanguage(),
                'defaultLanguage' => $this->get_default_language(),
                'homepageSlug' => $this->get_homepage_slug(),
                'homeUrl' => get_option( 'home' ),
                'languages' => $languages,
                'dict'    => $dict,
                'i18n'    => array(
                    'editText'      => __( 'Edit translation', 'livelang' ),
                    'original'      => __( 'Original', 'livelang' ),
                    'translated'    => __( 'Translated', 'livelang' ),
                    'global'        => __( 'Global (Site-Wide)', 'livelang' ),
                    'pageSpecific'  => __( 'Page Specific', 'livelang' ),
                    'save'          => __( 'Save', 'livelang' ),
                    'saveNext'      => __( 'Save & Next', 'livelang' ),
                    'saving'        => __( 'Saving...', 'livelang' ),
                    'saved'         => __( 'Saved!', 'livelang' ),
                    'cancel'        => __( 'Cancel', 'livelang' ),
                    'undo'          => __( 'Undo', 'livelang' ),
                    'redo'          => __( 'Redo', 'livelang' ),
                    'translate'     => __( 'Translate', 'livelang' ),
                    'translating'   => __( 'Translating...', 'livelang' ),
                    'active'        => __( 'Active', 'livelang' ),
                    'inactive'      => __( 'Inactive', 'livelang' ),
                    'editorActive'  => __( 'Live Translator Active', 'livelang' ),
                    'clickToEdit'   => __( 'Click string to translate', 'livelang' ),
                    'nextString'    => __( 'Next String', 'livelang' ),
                    'prevString'    => __( 'Previous String', 'livelang' ),
                    'shortcuts'     => __( 'Shortcuts', 'livelang' ),
                    'saveShortcut'  => __( 'Enter / ⌘+Enter: Save & Next', 'livelang' ),
                    'escShortcut'   => __( 'Esc: Cancel', 'livelang' ),
                    'tabShortcut'   => __( 'Tab: Next String', 'livelang' ),
                    'saveError'     => __( 'Error saving translation', 'livelang' ),
                ),
            )
        );
    }



    /**
     * Get all supported languages
     * Static cached for performance
     *
     * @return array
     */
    function get_current_language() {
        // Get languages from database
        $languages_data = $this->get_languages_for_frontend();
        $languages = array();
        foreach ( $languages_data as $lang ) {
            $languages[ $lang['code'] ] = $lang['label'];
        }

        return $languages;
    }

    /**
     * Get current selected language
     *
     * Priority:
     * 1. URL (?lang=fr)
     * 2. Cookie
     * 3. Default (en)
     *
     * @return string
     */
    function getCurrentLanguage() {
        if ( ! defined( 'LIVELANG_CURRENT_LANG' ) ) {
            $this->livelang_detect_language();
        }
        return LIVELANG_CURRENT_LANG;
    }

    function livelang_language_to_country($lang) {

        $map = apply_filters('livelang_language_flags', [

            // A
            'af' => 'za', // Afrikaans → South Africa
            'sq' => 'al',
            'am' => 'et',
            'ar' => 'sa',
            'hy' => 'am',
            'az' => 'az',

            // B
            'eu' => 'es',
            'be' => 'by',
            'bn' => 'bd',
            'bs' => 'ba',
            'bg' => 'bg',

            // C
            'ca' => 'es',
            'zh' => 'cn',
            'hr' => 'hr',
            'cs' => 'cz',

            // D
            'da' => 'dk',
            'nl' => 'nl',

            // E
            'en' => 'us', // default (can override)
            'et' => 'ee',

            // F
            'fi' => 'fi',
            'fr' => 'fr',

            // G
            'gl' => 'es',
            'ka' => 'ge',
            'de' => 'de',
            'el' => 'gr',
            'gu' => 'in',

            // H
            'he' => 'il',
            'hi' => 'in',
            'hu' => 'hu',

            // I
            'is' => 'is',
            'id' => 'id',
            'it' => 'it',

            // J
            'ja' => 'jp',

            // K
            'kn' => 'in',
            'kk' => 'kz',
            'km' => 'kh',
            'ko' => 'kr',

            // L
            'lo' => 'la',
            'lv' => 'lv',
            'lt' => 'lt',

            // M
            'mk' => 'mk',
            'ms' => 'my',
            'ml' => 'in',
            'mt' => 'mt',
            'mr' => 'in',
            'mn' => 'mn',

            // N
            'ne' => 'np',
            'nb' => 'no',
            'nn' => 'no',

            // P
            'fa' => 'ir',
            'pl' => 'pl',
            'pt' => 'pt',
            'pa' => 'in',

            // R
            'ro' => 'ro',
            'ru' => 'ru',

            // S
            'sr' => 'rs',
            'si' => 'lk',
            'sk' => 'sk',
            'sl' => 'si',
            'es' => 'es',
            'sw' => 'ke',
            'sv' => 'se',

            // T
            'ta' => 'in',
            'te' => 'in',
            'th' => 'th',
            'tr' => 'tr',

            // U
            'uk' => 'ua',
            'ur' => 'pk',
            'uz' => 'uz',

            // V
            'vi' => 'vn',

            // W
            'cy' => 'gb',
        ]);

        return $map[$lang] ?? 'un'; // fallback
    }

    function get_flag_url( $code ) {
        $country_code = $this->livelang_language_to_country($code);
        return plugins_url( 'assets/images/flags/' . $country_code . '.webp', LIVELANG_PLUGIN_FILE );
    }

    /**
     * Render the frontend editor bar
     */


    /**
     * Render language switcher dropdown
     */
    function livelang_language_switcher() {
        $languages = $this->get_current_language();
        $current   = $this->getCurrentLanguage();
        $current_label = isset( $languages[ $current ] ) ? $languages[ $current ] : 'Language';

        ob_start();

        echo '<div id="livelang-language-switcher" class="livelang-language-dropdown">';
        echo '<button class="livelang-language-toggle"><img src="' . esc_url($this->get_flag_url($current)) . '" class="livelang-current-language-flag"><span class="livelang-current-language-label">' . esc_html( $current_label ) . '</span> <span class="livelang-toggle-icon">▼</span></button>';
        echo '<ul class="livelang-language-list">';

        foreach ( $languages as $code => $label ) {
            $active_class = $current === $code ? 'active' : '';
            printf(
                '<li><a href="#" data-lang="%s" class="%s"><img src="%s" class="livelang-language-flag"><span class="livelang-language-label">%s</span></a></li>',
                esc_attr( $code ),
                esc_attr( $active_class ),
                esc_url( $this->get_flag_url( $code ) ),
                esc_html( $label )
            );
        }

        echo '</ul>';
        echo '</div>';
        return ob_get_clean();
    }

    function livelang_language_switcher_for_nav_menu() {
        $languages = $this->get_current_language();
        $current   = $this->getCurrentLanguage();
        $current_label = isset( $languages[ $current ] ) ? $languages[ $current ] : 'Language';

        ob_start();

        echo '<div id="livelang-language-switcher-for-nav-menu" class="livelang-language-dropdown">';
        echo '<button class="livelang-language-toggle"><img src="' . esc_url($this->get_flag_url($current)) . '" class="livelang-current-language-flag"><span class="livelang-language-label">' . esc_html( $current_label ) . '</span> <span class="livelang-toggle-icon">▼</span></button>';
        echo '<ul class="livelang-language-list-for-nav-menu sub-menu">';

        foreach ( $languages as $code => $label ) {
            $active_class = $current === $code ? 'active' : '';
            printf(
                '<li><a href="#" data-lang="%s" class="%s"><img src="%s" class="livelang-language-flag"><span class="livelang-language-label">%s</span></a></li>',
                esc_attr( $code ),
                esc_attr( $active_class ),
                esc_url( $this->get_flag_url( $code ) ),
                esc_html( $label )
            );
        }

        echo '</ul>';
        echo '</div>';
        return ob_get_clean();
    }

    /**
     * Filter menu item URLs to include language prefix
     */
    public function filter_menu_urls( $items ) {
        foreach ( $items as $item ) {
            if ( ! empty( $item->url ) ) {
                $item->url = $this->filter_url( $item->url );
            }
        }
        return $items;
    }

    public function filter_url( $url ) {
        if ( is_admin() || $this->is_builder_context() ) {
            return $url;
        }

        // Ignore empty or fragment URLs
        if ( empty( $url ) || strpos( $url, '#' ) === 0 || strpos( $url, 'mailto:' ) === 0 || strpos( $url, 'js:' ) === 0 ) {
            return $url;
        }

        $home_url = get_option('home');
        if ( strpos( $url, rtrim( $home_url, '/' ) ) !== 0 ) {
            return $url;
        }

        // Avoid adding prefix to REST API, wp-login, admin-ajax
        if ( strpos( $url, 'wp-json' ) !== false || strpos( $url, 'wp-login.php' ) !== false || strpos( $url, 'admin-ajax.php' ) !== false ) {
            return $url;
        }

        // Skip common file extensions (assets)
        if ( preg_match( '/\.(jpg|jpeg|png|gif|svg|webp|css|js|woff|woff2|ttf|eot|mp4|pdf)$/i', $url ) ) {
            return $url;
        }

        $current_lang = $this->getCurrentLanguage();
        $default_lang = $this->get_default_language();

        if ( empty( $current_lang ) || $current_lang === $default_lang ) {
            return $url;
        }

        $home_url_slash = trailingslashit( $home_url );
        $path = str_replace( $home_url_slash, '', $url );
        // Also check unslashed just in case
        $path = str_replace( $home_url, '', $path );

        // If it starts with the valid language prefix, skip
        if ( preg_match( '#^/?' . preg_quote( $current_lang, '#' ) . '(/|$)#', $path ) ) {
            return $url;
        }

        return $home_url_slash . $current_lang . '/' . ltrim( $path, '/' );
    }

    
    /**
     * Expand the single language switcher menu item into multiple language items
     * This allows WordPress walker to handle rendering and classes properly.
     *
     * @param array $items Menu items
     * @return array
     */
    public function expand_language_menu( $items ) {
        if ( is_admin() || empty( $items ) ) {
            return $items;
        }

        $new_items = array();
        $languages = $this->get_current_language();
        $current   = $this->getCurrentLanguage();
        
        static $id_counter = 0;

        foreach ( $items as $item ) {
            $new_items[] = $item;

            // Target our specific menu item
            if ( $item->url === '#livelang_switcher' || in_array( 'menu-item-language-switcher', (array) $item->classes ) ) {
                
                // 1. Update the parent item as the "Current Language" label
                $current_label = isset( $languages[ $current ] ) ? $languages[ $current ] : 'Language';
                $item->title = esc_html( $current_label );
                $item->url = '#'; 
                
                if ( ! in_array( 'menu-item-has-children', (array) $item->classes ) ) {
                    $item->classes[] = 'menu-item-has-children';
                }
                $item->classes[] = 'livelang-menu-item-parent';
                $item->classes[] = 'livelang-language-dropdown';
                $item->livelang_type = 'parent';

                $parent_id = ! empty( $item->db_id ) ? $item->db_id : $item->ID;

                // 2. Add ALL languages as children (Submenu)
                $order_base = 10000; // prevent collision with real menu items

                foreach ( $languages as $code => $label ) {
                    $lang_item = new stdClass();

                    $lang_item->ID = 2000000 + ( ++$id_counter );
                    $lang_item->db_id = $lang_item->ID;
                    $lang_item->post_type = 'nav_menu_item';
                    $lang_item->post_status = 'publish';
                    $lang_item->type = 'custom';
                    $lang_item->object = 'custom';
                    $lang_item->object_id = $lang_item->ID;

                    $lang_item->title = $label;
                    $lang_item->url = '#';
                    $lang_item->menu_item_parent = (int) $item->ID;
                    $lang_item->menu_order = $item->menu_order + 100 + $id_counter;

                    $lang_item->classes = array(
                        'menu-item',
                        'menu-item-type-custom',
                        'menu-item-object-custom',
                        'livelang-language-item',
                        'livelang-lang-' . $code
                    );

                    $lang_item->livelang_code = $code;
                    $lang_item->livelang_type = 'child';


                    $new_items[] = $lang_item;
                }


            }
        }
        return $new_items;
    }

    /**
     * Add data-lang attribute to language switcher links
     */
    public function nav_menu_link_attributes( $atts, $item, $args, $depth ) {
        // Try to get lang code from property first
        if ( isset( $item->livelang_code ) ) {
            $atts['data-lang'] = $item->livelang_code;
        } else {
            // Fallback: check classes (useful if property was lost during WP setup)
            foreach ( (array) $item->classes as $class ) {
                if ( is_string( $class ) && strpos( $class, 'livelang-lang-' ) === 0 ) {
                    $atts['data-lang'] = str_replace( 'livelang-lang-', '', $class );
                    break;
                }
            }
        }
        
        // Add toggle class to the parent link for JS compatibility
        if ( property_exists( $item, 'classes' ) && is_array( $item->classes ) && in_array( 'livelang-menu-item-parent', $item->classes ) ) {
            $atts['class'] = ( isset( $atts['class'] ) ? $atts['class'] . ' ' : '' ) . 'livelang-language-toggle-inside-menu';
        }
        
        return $atts;
    }

    /**
     * Fallback for custom walkers that don't use nav_menu_link_attributes
     */
    public function livelang_menu_item_output( $item_output, $item, $depth, $args ) {
        if ( is_admin() ) {
            return $item_output;
        }

        $lang_code = '';
        if ( isset( $item->livelang_code ) ) {
            $lang_code = $item->livelang_code;
        } else {
            foreach ( (array) $item->classes as $class ) {
                if ( is_string( $class ) && strpos( $class, 'livelang-lang-' ) === 0 ) {
                    $lang_code = str_replace( 'livelang-lang-', '', $class );
                    break;
                }
            }
        }

        if ( $lang_code ) {
            $flag_url = $this->get_flag_url( $lang_code );
            $flag_html = '<img src="' . esc_url( $flag_url ) . '" class="livelang-language-flag" style="width:25px; height:15px; margin-right:10px; vertical-align:middle;">';
            
            // Inject data-lang attribute into the first <a> tag if not present
            if ( strpos( $item_output, 'data-lang=' ) === false ) {
                $item_output = preg_replace( '/<a /', '<a data-lang="' . esc_attr( $lang_code ) . '" ', $item_output, 1 );
            }

            // Inject flag before the title inside the <a> tag
            $item_output = preg_replace( '/(<a[^>]*>)(.*)(<\/a>)/isU', '$1' . $flag_html . '<span class="livelang-language-label">$2</span>$3', $item_output );
        }

        // Inject toggle class for parent if missed
        if ( property_exists( $item, 'classes' ) && is_array( $item->classes ) && in_array( 'livelang-menu-item-parent', $item->classes ) ) {
             if ( strpos( $item_output, 'livelang-language-toggle' ) === false ) {
                 $item_output = preg_replace( '/class="([^"]*)"/', 'class="$1 livelang-language-toggle-inside-menu"', $item_output, 1 );
             }
        }

        return $item_output;
    }

    /**
     * Render the floating language switcher in the footer
     */
    public function render_floating_switcher() {
        if ( is_admin() ) {
            return;
        }

        $settings = get_option( 'livelang_settings', array() );
        $enabled = isset( $settings['switcher_enabled'] ) ? (bool) $settings['switcher_enabled'] : false;
        
        if ( ! $enabled ) {
            return;
        }
        
        $languages = $this->get_languages_for_frontend();
        if ( count( $languages ) <= 1 ) {
            return;
        }

        $current_lang = $this->getCurrentLanguage();
        $position     = isset( $settings['switcher_position'] ) ? $settings['switcher_position'] : 'bottom-right';
        if ( ! in_array( $position, array( 'bottom-right', 'bottom-left', 'top-right', 'top-left' ), true ) ) {
            $position = 'bottom-right';
        }
        $bg_color     = isset( $settings['switcher_bg_color'] ) ? $settings['switcher_bg_color'] : '#ffffff';
        $text_color   = isset( $settings['switcher_text_color'] ) ? $settings['switcher_text_color'] : '#333333';
        $accent_color = isset( $settings['switcher_accent_color'] ) ? $settings['switcher_accent_color'] : '#2563eb';

        $current_label = '';
        foreach ( $languages as $lang ) {
            if ( $lang['code'] === $current_lang ) {
                $current_label = $lang['label'];
                break;
            }
        }
        
        if ( empty( $current_label ) ) {
            $current_label = $languages[0]['label'];
        }

        ?>
        <style>
            :root {
                --livelang-bg: <?php echo esc_attr( $bg_color ); ?>;
                --livelang-text: <?php echo esc_attr( $text_color ); ?>;
                --livelang-accent: <?php echo esc_attr( $accent_color ); ?>;
            }
            .livelang-floating-switcher {
                position: fixed;
                z-index: 999999;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
                margin: 20px;
                user-select: none;
                display: flex;
                flex-direction: column;
            }
            .livelang-floating-switcher.bottom-right { bottom: 0; right: 0; flex-direction: column-reverse; }
            .livelang-floating-switcher.bottom-left { bottom: 0; left: 0; flex-direction: column-reverse; }
            .livelang-floating-switcher.top-right { top: 0; right: 0; }
            .livelang-floating-switcher.top-left { top: 0; left: 0; }
            
            .livelang-floating-switcher.bottom-right .livelang-switcher-dropdown,
            .livelang-floating-switcher.bottom-left .livelang-switcher-dropdown {
                margin-top: 0;
                margin-bottom: 10px;
            }
            
            .livelang-switcher-btn {
                background: var(--livelang-bg);
                color: var(--livelang-text);
                padding: 10px 16px;
                border-radius: 30px;
                display: flex;
                align-items: center;
                gap: 10px;
                box-shadow: 0 4px 15px rgba(0,0,0,0.15);
                cursor: pointer;
                border: 1px solid rgba(0,0,0,0.05);
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                font-weight: 600;
                font-size: 14px;
            }
            .livelang-switcher-btn:hover {
                transform: translateY(-2px);
                box-shadow: 0 6px 20px rgba(0,0,0,0.2);
            }
            .livelang-switcher-flag {
                width: 20px;
                height: auto;
                border-radius: 2px;
                display: block;
            }
            .livelang-switcher-arrow {
                font-size: 10px;
                opacity: 0.6;
                transition: transform 0.3s;
            }
            .livelang-floating-switcher.is-open .livelang-switcher-arrow {
                transform: rotate(180deg);
            }
            .livelang-switcher-dropdown {
                position: absolute;
                background: var(--livelang-bg);
                border-radius: 12px;
                box-shadow: 0 10px 30px rgba(0,0,0,0.2);
                min-width: 180px;
                overflow: hidden;
                opacity: 0;
                visibility: hidden;
                transform: translateY(10px) scale(0.95);
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                border: 1px solid rgba(0,0,0,0.05);
            }
            .bottom-right .livelang-switcher-dropdown, .top-right .livelang-switcher-dropdown { right: 0; }
            .bottom-left .livelang-switcher-dropdown, .top-left .livelang-switcher-dropdown { left: 0; }
            
            .bottom-right .livelang-switcher-dropdown, .bottom-left .livelang-switcher-dropdown { bottom: calc(100% + 15px); }
            .top-right .livelang-switcher-dropdown, .top-left .livelang-switcher-dropdown { top: calc(100% + 15px); }
            
            .livelang-floating-switcher.is-open .livelang-switcher-dropdown {
                opacity: 1;
                visibility: visible;
                transform: translateY(0) scale(1);
            }
            .livelang-dropdown-item {
                display: flex;
                align-items: center;
                gap: 12px;
                padding: 12px 16px;
                color: var(--livelang-text);
                text-decoration: none;
                transition: all 0.2s;
                font-size: 14px;
                border-left: 3px solid transparent;
            }
            .livelang-dropdown-item:hover {
                background: rgba(0,0,0,0.03);
                color: var(--livelang-accent);
            }
            .livelang-dropdown-item.active {
                font-weight: 700;
                border-left-color: var(--livelang-accent);
                color: var(--livelang-accent);
                background: rgba(0,0,0,0.02);
            }
        </style>
        <div id="livelang-floating-switcher" class="livelang-floating-switcher <?php echo esc_attr( $position ); ?>">
            <div class="livelang-switcher-btn">
                <img src="<?php echo esc_url( $this->get_flag_url( $current_lang ) ); ?>" class="livelang-switcher-flag">
                <span class="livelang-switcher-label"><?php echo esc_html( $current_label ); ?></span>
                <span class="livelang-switcher-arrow">▼</span>
            </div>
            <div class="livelang-switcher-dropdown">
                <?php foreach ( $languages as $lang ) : 
                    $is_active = ( $lang['code'] === $current_lang );
                    ?>
                    <a href="<?php echo esc_url( $this->get_language_url( $lang['code'] ) ); ?>" class="livelang-dropdown-item <?php echo $is_active ? 'active' : ''; ?>">
                        <img src="<?php echo esc_url( $this->get_flag_url( $lang['code'] ) ); ?>" class="livelang-switcher-flag">
                        <span><?php echo esc_html( $lang['label'] ); ?></span>
                    </a>
                <?php endforeach; ?>
            </div>
        </div>
        <script>
            /**
             * LiveLang Floating Switcher Logic
             * Encapsulated in an IIFE to avoid global scope pollution
             */
            (function() {
                document.addEventListener('DOMContentLoaded', function() {
                    const switcher = document.getElementById('livelang-floating-switcher');
                    if (!switcher) return;
                    
                    const btn = switcher.querySelector('.livelang-switcher-btn');
                    
                    btn.addEventListener('click', function(e) {
                        e.stopPropagation();
                        switcher.classList.toggle('is-open');
                    });
                    
                    document.addEventListener('click', function(e) {
                        // Close only if clicking outside
                        if (!switcher.contains(e.target)) {
                            switcher.classList.remove('is-open');
                        }
                    });

                    // Handle Esc key to close
                    document.addEventListener('keydown', function(e) {
                        if (e.key === 'Escape') {
                            switcher.classList.remove('is-open');
                        }
                    });
                });
            })();
        </script>
        <?php
    }
}
