<?php
/**
 * Administrative menu registration and view rendering.
 *
 * @package WPR_Translate
 */

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

require_once WPR_TRANSLATE_PATH . 'includes/admin/views/languages-helpers.php';
require_once WPR_TRANSLATE_PATH . 'includes/admin/views/string-helpers.php';
require_once WPR_TRANSLATE_PATH . 'includes/admin/settings-helpers.php';
require_once WPR_TRANSLATE_PATH . 'includes/admin/language-packs.php';
require_once WPR_TRANSLATE_PATH . 'includes/admin/string-query.php';
require_once WPR_TRANSLATE_PATH . 'includes/admin/string-scanner.php';
require_once WPR_TRANSLATE_PATH . 'includes/admin/theme-strings.php';
require_once WPR_TRANSLATE_PATH . 'includes/admin/reset-settings.php';
require_once WPR_TRANSLATE_PATH . 'includes/admin/views/overview.php';

/**
 * Bootstrap the admin integration.
 */
function wpr_translate_admin_bootstrap() {
    // Track bootstrap state to avoid duplicate hooks.
    static $bootstrapped = false;

    if ( $bootstrapped ) {
        // Hooks already registered.
        return;
    }

    $bootstrapped = true;

    // Register admin menu entries and handlers.
    add_action( 'admin_menu', 'wpr_translate_admin_register_menus' );
    add_action( 'admin_init', 'wpr_translate_admin_handle_strings_actions', 5 );
    add_action( 'admin_init', 'wpr_translate_admin_handle_custom_field_rules_actions' );
    add_action( 'admin_init', 'wpr_translate_admin_handle_menu_sync_submission' );
    add_action( 'admin_init', 'wpr_translate_admin_handle_reset_settings' );
    add_action( 'admin_init', 'wpr_translate_admin_register_settings' );
    add_action( 'wp_ajax_wpr_translate_save_translation', 'wpr_translate_admin_ajax_save_translation' );
    add_action( 'wp_ajax_wpr_translate_auto_translate_strings', 'wpr_translate_admin_ajax_auto_translate_strings' );
    add_action( 'wp_ajax_wpr_translate_save_cf_rule', 'wpr_translate_admin_ajax_save_custom_field_rule' );
    add_action( 'wp_ajax_wpr_translate_save_post_type_preferences', 'wpr_translate_admin_ajax_save_post_type_preferences' );
    add_action( 'wp_ajax_wpr_translate_get_posts_for_auto_translation', 'wpr_translate_admin_ajax_get_posts_for_auto_translation' );

    if ( function_exists( 'wpr_translate_bootstrap_admin_bar_language_switcher' ) ) {
        // Prepare the admin bar dropdown that controls the admin content language.
        wpr_translate_bootstrap_admin_bar_language_switcher();
    }
}

/**
 * Register plugin admin menus.
 */
function wpr_translate_admin_register_menus() {
    // Register the top-level admin menu.
    add_menu_page(
        __( 'WPEstate Translate', 'wpr-translate' ),
        __( 'WPEstate Translate', 'wpr-translate' ),
        'manage_options',
        'wpr-translate-overview',
        'wpr_translate_admin_render_overview_page',
        'dashicons-translation',
        59
    );

    add_submenu_page(
        'wpr-translate-overview',
        __( 'Start Here', 'wpr-translate' ),
        __( 'Start Here', 'wpr-translate' ),
        'manage_options',
        'wpr-translate-overview',
        'wpr_translate_admin_render_overview_page'
    );

    add_submenu_page(
        'wpr-translate-overview',
        __( 'Languages Manager', 'wpr-translate' ),
        __( 'Languages', 'wpr-translate' ),
        'manage_options',
        'wpr-translate-languages',
        'wpr_translate_admin_render_languages_page'
    );

    add_submenu_page(
        'wpr-translate-overview',
        __( 'Theme & Plugins Strings', 'wpr-translate' ),
        __( 'Theme & Plugins Strings', 'wpr-translate' ),
        'manage_options',
        'wpr-translate-strings',
        'wpr_translate_admin_render_strings_page'
    );

    add_submenu_page(
        'wpr-translate-overview',
        __( 'Taxonomy Translation', 'wpr-translate' ),
        __( 'Taxonomy Translation', 'wpr-translate' ),
        'manage_options',
        'wpr-translate-taxonomies',
        'wpr_translate_admin_render_taxonomy_settings_page'
    );

    add_submenu_page(
        'wpr-translate-overview',
        __( 'Custom Field Rules', 'wpr-translate' ),
        __( 'Custom Field Rules', 'wpr-translate' ),
        'manage_options',
        'wpr-translate-custom-fields',
        'wpr_translate_admin_render_custom_field_rules_page'
    );

    add_submenu_page(
        'wpr-translate-overview',
        __( 'Automatic Translation', 'wpr-translate' ),
        __( 'Automatic Translation', 'wpr-translate' ),
        'manage_options',
        'wpr-translate-automatic-translation',
        'wpr_translate_admin_render_auto_translation_page'
    );


    add_submenu_page(
        'wpr-translate-overview',
        __( 'Menu Synchronization', 'wpr-translate' ),
        __( 'Menu Synchronization', 'wpr-translate' ),
        'manage_options',
        'wpr-translate-menu-sync',
        'wpr_translate_admin_render_menu_sync_page'
    );


    add_submenu_page(
        'wpr-translate-overview',
        __( 'Translation Settings', 'wpr-translate' ),
        __( 'Settings', 'wpr-translate' ),
        'manage_options',
        'wpr-translate-settings',
        'wpr_translate_admin_render_settings_page'
    );
}

/**
 * Register settings used throughout the admin screens.
 */
function wpr_translate_admin_register_settings() {
    // Register admin settings groups and sanitizers.
    register_setting( 'wpr_translate_languages', 'wpr_translate_languages', 'wpr_translate_admin_sanitize_languages' );
    register_setting( 'wpr_translate_settings', 'wpr_translate_settings', 'wpr_translate_admin_sanitize_settings' );
    register_setting( 'wpr_translate_glossary', 'wpr_translate_glossary', 'wpr_translate_admin_sanitize_glossary' );
    register_setting( 'wpr_translate_auto_translation', 'wpr_translate_auto_translation', 'wpr_translate_admin_sanitize_auto_translation_settings' );
}

/**
 * Render the languages manager.
 */
function wpr_translate_admin_render_languages_page() {
    // Load stored languages and catalog data.
    $languages = get_option( 'wpr_translate_languages', array() );
    $catalog   = get_option( 'wpr_translate_language_catalog', array() );

    // Ensure the catalog always contains the full hardcoded set while preserving stored overrides.
    if ( function_exists( 'wpr_translate_activator_get_predefined_languages_catalog' ) ) {
        $predefined_catalog      = wpr_translate_activator_get_predefined_languages_catalog();
        $catalog_by_code         = array();
        $predefined_catalog_by_code = array();

        foreach ( $predefined_catalog as $entry ) {
            if ( ! is_array( $entry ) || empty( $entry['code'] ) ) {
                continue;
            }

            $predefined_catalog_by_code[ $entry['code'] ] = $entry;
            $catalog_by_code[ $entry['code'] ]            = $entry;
        }

        if ( is_array( $catalog ) ) {
            foreach ( $catalog as $entry ) {
                if ( ! is_array( $entry ) || empty( $entry['code'] ) ) {
                    continue;
                }

                $catalog_by_code[ $entry['code'] ] = $entry;
            }
        }

        if ( $catalog_by_code !== $predefined_catalog_by_code ) {
            // Persist any merged catalog entries.
            $catalog = array_values( $catalog_by_code );
            update_option( 'wpr_translate_language_catalog', $catalog );
        } else {
            $catalog = $predefined_catalog;
        }
    }

    // Prepare view data and render.
    $data = wpr_translate_admin_prepare_languages_view_data( $languages, $catalog );

    wpr_translate_admin_render_view( 'languages', $data );
}

/**
 * Render the strings overview.
 */
function wpr_translate_admin_render_strings_page() {
    // Track the current domain filters for the strings view.
    $widget_domain = '';
    $admin_domain  = '';
    $widget_import = wpr_translate_admin_import_theme_widget_strings();
    $admin_import  = wpr_translate_admin_import_theme_admin_strings();

    if ( is_wp_error( $widget_import ) ) {
        add_settings_error(
            'wpr_translate_strings',
            'wpr_translate_theme_widget_error',
            $widget_import->get_error_message(),
            'error'
        );
    } else {
        if ( isset( $widget_import['domain'] ) ) {
            $widget_domain = sanitize_key( $widget_import['domain'] );
        }

        if ( isset( $widget_import['status'] ) && 'imported' === $widget_import['status'] ) {
            $count = isset( $widget_import['count'] ) ? (int) $widget_import['count'] : 0;

            add_settings_error(
                'wpr_translate_strings',
                'wpr_translate_theme_widget_imported',
                sprintf(
                    _n(
                        'Loaded %d widget string from the theme file.',
                        'Loaded %d widget strings from the theme file.',
                        $count,
                        'wpr-translate'
                    ),
                    $count
                ),
                'updated'
            );
        } elseif ( isset( $widget_import['status'] ) && 'up_to_date' === $widget_import['status'] ) {
            add_settings_error(
                'wpr_translate_strings',
                'wpr_translate_theme_widget_up_to_date',
                __( 'The theme widget strings are already up to date.', 'wpr-translate' ),
                'updated'
            );
        }
    }

    if ( '' === $widget_domain ) {
        // Fall back to the stored widget domain when available.
        $stored_widget_domain = get_option( 'wpr_translate_theme_widget_strings_domain', '' );

        if ( is_string( $stored_widget_domain ) ) {
            $widget_domain = sanitize_key( $stored_widget_domain );
        }
    }

    if ( is_wp_error( $admin_import ) ) {
        add_settings_error(
            'wpr_translate_strings',
            'wpr_translate_theme_admin_error',
            $admin_import->get_error_message(),
            'error'
        );
    } else {
        if ( isset( $admin_import['domain'] ) ) {
            $admin_domain = sanitize_key( $admin_import['domain'] );
        }

        if ( isset( $admin_import['status'] ) && 'imported' === $admin_import['status'] ) {
            $count = isset( $admin_import['count'] ) ? (int) $admin_import['count'] : 0;

            add_settings_error(
                'wpr_translate_strings',
                'wpr_translate_theme_admin_imported',
                sprintf(
                    _n(
                        'Loaded %d admin string from the theme file.',
                        'Loaded %d admin strings from the theme file.',
                        $count,
                        'wpr-translate'
                    ),
                    $count
                ),
                'updated'
            );
        }
    }

    if ( '' === $admin_domain ) {
        // Fall back to the stored admin strings domain.
        $stored_admin_domain = get_option( 'wpr_translate_theme_admin_strings_domain', '' );

        if ( is_string( $stored_admin_domain ) ) {
            $admin_domain = sanitize_key( $stored_admin_domain );
        }
    }

    // Read filters and pagination from the request.
    $filters = wpr_translate_admin_get_strings_filters_from_request();

    // Only apply automatic domain defaults when the request does not supply a domain.
    $domain_param_provided = array_key_exists( 'domain', $_GET );

    if ( ! $domain_param_provided ) {
        if ( '' !== $admin_domain && ( ! isset( $filters['domain'] ) || '' === $filters['domain'] ) ) {
            $filters['domain'] = $admin_domain;
        } elseif ( '' !== $widget_domain && ( ! isset( $filters['domain'] ) || '' === $filters['domain'] ) ) {
            $filters['domain'] = $widget_domain;
        }
    }

    $pagination = wpr_translate_admin_get_strings_pagination_from_request();
    $strings    = wpr_translate_admin_get_strings_for_view( $filters, $pagination );

    // Build the view data payload.
    $data = array(
        'strings'   => isset( $strings['strings'] ) ? $strings['strings'] : array(),
        'languages' => get_option( 'wpr_translate_languages', array() ),
        'filters'   => $filters,
        'domains'   => isset( $strings['domains'] ) ? $strings['domains'] : array(),
        'pagination'=> isset( $strings['pagination'] ) ? $strings['pagination'] : array(),
    );

    // Normalize view data and render.
    $data = wpr_translate_admin_prepare_strings_view_data( $data );

    wpr_translate_admin_render_view( 'string-translation', $data );
}

/**
 * Render settings screen.
 */
function wpr_translate_admin_render_settings_page() {
    // Prepare settings view data.
    $data = array(
        'settings'  => get_option( 'wpr_translate_settings', array() ),
        'languages' => get_option( 'wpr_translate_languages', array() ),
    );

    wpr_translate_admin_render_view( 'settings', $data );
}

/**
 * Render the menu synchronization page.
 */
function wpr_translate_admin_render_menu_sync_page() {
    // Prepare menu sync view data.
    $data = wpr_translate_admin_prepare_menu_sync_view_data();

    wpr_translate_admin_render_view( 'menu-synchronization', $data );
}

/**
 * Render automatic translation settings screen.
 */
function wpr_translate_admin_render_auto_translation_page() {
    // Gather available post types that have a UI so they can be displayed in the automatic translation screen.
    $post_types       = get_post_types( array( 'show_ui' => true ), 'objects' );
    $post_type_entries = array();
    $excluded_post_types = function_exists( 'wpr_translate_get_auto_translation_excluded_post_types' )
        ? wpr_translate_get_auto_translation_excluded_post_types()
        : array();

    // Fetch active languages so users can select a target language for automatic translation.
    $active_languages = function_exists( 'wpr_translate_get_active_languages' ) ? wpr_translate_get_active_languages( false ) : array();
    $language_choices = array();

    if ( ! empty( $active_languages ) ) {
        foreach ( $active_languages as $language ) {
            if ( ! is_array( $language ) ) {
                continue;
            }

            $language_code = isset( $language['code'] ) ? sanitize_text_field( $language['code'] ) : '';
            $language_slug = isset( $language['slug'] ) ? sanitize_title( $language['slug'] ) : '';
            $language_name = isset( $language['name'] ) ? sanitize_text_field( $language['name'] ) : $language_code;
            $language_value = '' !== $language_slug ? strtolower( $language_slug ) : strtolower( $language_code );

            if ( '' === $language_name ) {
                $language_name = $language_code ? $language_code : $language_slug;
            }

            if ( '' === $language_name || '' === $language_value ) {
                continue;
            }

            $language_choices[] = array(
                'value' => $language_value,
                'label' => $language_name,
            );
        }
    }

    // Load saved post type preferences so the UI can reflect existing choices.
    $auto_translation_settings = get_option( 'wpr_translate_auto_translation', array() );
    $auto_translation_settings = is_array( $auto_translation_settings ) ? $auto_translation_settings : array();
    $post_type_preferences     = array();
    $target_language_choice    = isset( $auto_translation_settings['target_language'] ) ? strtolower( sanitize_text_field( $auto_translation_settings['target_language'] ) ) : '';

    if ( isset( $auto_translation_settings['post_types'] ) && is_array( $auto_translation_settings['post_types'] ) ) {
        // Normalize stored post type preferences.
        foreach ( $auto_translation_settings['post_types'] as $post_type_name => $preference ) {
            $post_type_preferences[ sanitize_key( $post_type_name ) ] = 'yes' === $preference ? 'yes' : 'no';
        }
    }

    if ( ! empty( $post_types ) ) {
        // Build the post type choices list for the view.
        foreach ( $post_types as $post_type ) {
            if ( empty( $post_type->name ) ) {
                continue;
            }

            $post_type_name  = sanitize_key( (string) $post_type->name );
            $post_type_label = ( isset( $post_type->labels->singular_name ) && $post_type->labels->singular_name ) ? (string) $post_type->labels->singular_name : (string) $post_type->label;
            $label_key       = strtolower( trim( sanitize_text_field( $post_type_label ) ) );

            // Exclude non-translatable/system and transactional post types from automatic translation.
            if ( in_array( $post_type_name, $excluded_post_types, true ) ) {
                continue;
            }

            if ( in_array( $label_key, array( 'floating element', 'media', 'navigation menu', 'pattern', 'searches', 'template' ), true ) ) {
                continue;
            }

            $post_type_entries[] = array(
                'name'   => $post_type_name,
                'label'  => sanitize_text_field( $post_type_label ),
                'choice' => isset( $post_type_preferences[ $post_type_name ] ) ? $post_type_preferences[ $post_type_name ] : 'no',
            );
        }
    }

    if ( ! empty( $post_type_entries ) ) {
        // Keep "page" at the end because those translations are typically the slowest.
        usort(
            $post_type_entries,
            static function ( $left, $right ) {
                $left_name  = isset( $left['name'] ) ? sanitize_key( (string) $left['name'] ) : '';
                $right_name = isset( $right['name'] ) ? sanitize_key( (string) $right['name'] ) : '';

                if ( 'page' === $left_name && 'page' !== $right_name ) {
                    return 1;
                }

                if ( 'page' !== $left_name && 'page' === $right_name ) {
                    return -1;
                }

                return strcasecmp(
                    isset( $left['label'] ) ? (string) $left['label'] : '',
                    isset( $right['label'] ) ? (string) $right['label'] : ''
                );
            }
        );

        foreach ( $post_type_entries as $index => $entry ) {
            $entry_name = isset( $entry['name'] ) ? sanitize_key( (string) $entry['name'] ) : '';

            if ( 'page' !== $entry_name ) {
                continue;
            }

            $post_type_entries[ $index ]['label'] = __( 'Pages (do these last)', 'wpr-translate' );
        }
    }

    // Assemble the view data payload.
    $data = array(
        'auto_translation' => $auto_translation_settings,
        'post_types'       => $post_type_entries,
        'languages'        => $language_choices,
        'target_language'  => $target_language_choice,
    );

    wpr_translate_admin_render_view( 'automatic-translation', $data );
}

/**
 * Persist a single string translation from the inline strings editor.
 *
 * @return void
 */
function wpr_translate_admin_ajax_save_translation() {
    global $wpdb;

    // Reject requests with invalid nonce.
    if ( ! check_ajax_referer( 'wpr_translate_update_string', 'nonce', false ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'Security check failed. Please refresh and try again.', 'wpr-translate' ),
            ),
            403
        );
    }

    // Restrict updates to users allowed to manage plugin settings.
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'You are not allowed to update translations.', 'wpr-translate' ),
            ),
            403
        );
    }

    $context     = isset( $_POST['context'] ) ? sanitize_text_field( wp_unslash( $_POST['context'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    $name        = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    $language    = isset( $_POST['language'] ) ? strtolower( sanitize_key( wp_unslash( $_POST['language'] ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    $translation = isset( $_POST['translation'] ) ? wp_kses_post( wp_unslash( $_POST['translation'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    $languages   = get_option( 'wpr_translate_languages', array() );
    $default_language = '';

    if ( function_exists( 'wpr_translate_admin_get_default_language_code' ) ) {
        $default_language = strtolower( sanitize_key( wpr_translate_admin_get_default_language_code( $languages ) ) );
    }

    if ( '' === $context || '' === $name || '' === $language ) {
        wp_send_json_error(
            array(
                'message' => __( 'Missing translation payload.', 'wpr-translate' ),
            ),
            400
        );
    }

    if ( ! function_exists( 'wpr_translate_admin_strings_table_exists' ) || ! wpr_translate_admin_strings_table_exists() ) {
        wp_send_json_error(
            array(
                'message' => __( 'The translation strings table could not be found.', 'wpr-translate' ),
            ),
            500
        );
    }

    $table = $wpdb->prefix . 'wpestate_translation_strings';
    $is_default_language = ( '' !== $default_language && $language === $default_language );

    if ( $is_default_language && function_exists( 'wpr_translate_admin_set_option_value_by_path' ) ) {
        $option_value = get_option( $context, array() );

        if ( is_array( $option_value ) ) {
            wpr_translate_admin_set_option_value_by_path( $option_value, $name, $translation );
            update_option( $context, $option_value );
        }
    }

    // Reuse existing row values where possible.
    $row = $wpdb->get_row(
        $wpdb->prepare(
            "SELECT string_id, value, md5 FROM {$table} WHERE context = %s AND name = %s AND language_code = %s LIMIT 1",
            $context,
            $name,
            $language
        ),
        ARRAY_A
    );

    if ( empty( $row ) ) {
        $fallback = $wpdb->get_row(
            $wpdb->prepare(
                "SELECT value, md5 FROM {$table} WHERE context = %s AND name = %s LIMIT 1",
                $context,
                $name
            ),
            ARRAY_A
        );

        $source_value = isset( $fallback['value'] ) ? (string) $fallback['value'] : '';
        $source_md5   = isset( $fallback['md5'] ) && '' !== $fallback['md5'] ? (string) $fallback['md5'] : md5( $source_value );

        if ( $is_default_language ) {
            $source_value = $translation;
            $source_md5   = md5( $translation );
        }

        $inserted = $wpdb->insert(
            $table,
            array(
                'context'       => $context,
                'name'          => $name,
                'value'         => $source_value,
                'md5'           => $source_md5,
                'language_code' => $language,
                'translation'   => $translation,
                'status'        => $is_default_language ? 1 : ( '' === trim( wp_strip_all_tags( (string) $translation ) ) ? 0 : 1 ),
            ),
            array( '%s', '%s', '%s', '%s', '%s', '%s', '%d' )
        );

        if ( false === $inserted ) {
            wp_send_json_error(
                array(
                    'message' => __( 'Could not save translation.', 'wpr-translate' ),
                ),
                500
            );
        }
    } else {
        $update_data = array(
            'translation' => $translation,
        );
        $update_format = array( '%s' );

        if ( $is_default_language ) {
            $update_data['value']  = $translation;
            $update_data['md5']    = md5( $translation );
            $update_data['status'] = 1;
            $update_format[]       = '%s';
            $update_format[]       = '%s';
            $update_format[]       = '%d';
        }

        $updated = $wpdb->update(
            $table,
            $update_data,
            array(
                'string_id' => (int) $row['string_id'],
            ),
            $update_format,
            array( '%d' )
        );

        if ( false === $updated ) {
            wp_send_json_error(
                array(
                    'message' => __( 'Could not save translation.', 'wpr-translate' ),
                ),
                500
            );
        }
    }

    $status = '' === trim( wp_strip_all_tags( (string) $translation ) ) ? 'untranslated' : 'translated';

    wp_send_json_success(
        array(
            'translation' => $translation,
            'status'      => $status,
        )
    );
}

/**
 * AJAX handler to automatically translate theme/admin strings for one language.
 *
 * @return void
 */
function wpr_translate_admin_ajax_auto_translate_strings() {
    global $wpdb;

    if ( ! check_ajax_referer( 'wpr_translate_auto_translate_strings', 'nonce', false ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'Invalid request. Please refresh and try again.', 'wpr-translate' ),
            ),
            400
        );
    }

    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'You are not allowed to run automatic string translation.', 'wpr-translate' ),
            ),
            403
        );
    }

    if ( ! function_exists( 'wpr_translate_openai_translate_json_payload' ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'OpenAI translation helper is unavailable.', 'wpr-translate' ),
            ),
            500
        );
    }

    $target_language = isset( $_POST['target_language'] ) ? strtolower( sanitize_key( wp_unslash( $_POST['target_language'] ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    $target_domain   = isset( $_POST['target_domain'] ) ? sanitize_text_field( wp_unslash( $_POST['target_domain'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing

    $active_languages = function_exists( 'wpestate_translation_get_active_languages' ) ? wpestate_translation_get_active_languages() : array();
    $language_choices = array();
    $language_list    = array();

    foreach ( $active_languages as $language_key => $language_data ) {
        if ( is_string( $language_key ) && '' !== $language_key ) {
            $language_choices[] = strtolower( sanitize_key( $language_key ) );
        }

        if ( ! is_array( $language_data ) ) {
            continue;
        }

        $language_list[] = $language_data;

        $language_code = isset( $language_data['code'] ) ? strtolower( sanitize_key( $language_data['code'] ) ) : '';
        $language_slug = isset( $language_data['slug'] ) ? strtolower( sanitize_key( $language_data['slug'] ) ) : '';

        if ( '' !== $language_code ) {
            $language_choices[] = $language_code;
        }

        if ( '' !== $language_slug ) {
            $language_choices[] = $language_slug;
        }
    }

    if ( empty( $language_list ) ) {
        $language_list = get_option( 'wpr_translate_languages', array() );
    }

    $language_choices = array_values( array_unique( array_filter( $language_choices ) ) );

    if ( '' === $target_language || ! in_array( $target_language, $language_choices, true ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'Please select a valid target language.', 'wpr-translate' ),
            ),
            400
        );
    }

    $default_language = '';

    if ( function_exists( 'wpr_translation_get_default_language_code' ) ) {
        $default_language = strtolower( sanitize_key( wpr_translation_get_default_language_code( $language_list ) ) );
    }

    if ( '' === $default_language || $target_language === $default_language ) {
        wp_send_json_error(
            array(
                'message' => __( 'Please select a non-default target language.', 'wpr-translate' ),
            ),
            400
        );
    }

    if ( ! function_exists( 'wpr_translate_admin_strings_table_exists' ) || ! wpr_translate_admin_strings_table_exists() ) {
        wp_send_json_error(
            array(
                'message' => __( 'The translation strings table could not be found.', 'wpr-translate' ),
            ),
            500
        );
    }

    $admin_domain = sanitize_key( (string) get_option( 'wpr_translate_theme_admin_strings_domain', 'wpresidence_admin' ) );
    if ( '' === $admin_domain ) {
        $admin_domain = 'wpresidence_admin';
    }

    $contexts = array();

    if ( '' !== $target_domain ) {
        $contexts[] = $target_domain;
    } elseif ( function_exists( 'wpr_translate_admin_get_string_domains' ) ) {
        $contexts = array_keys( wpr_translate_admin_get_string_domains() );
    }

    $contexts = array_values( array_unique( array_filter( array_map( 'sanitize_text_field', $contexts ) ) ) );

    if ( empty( $contexts ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'No string domains were found for automatic translation.', 'wpr-translate' ),
            ),
            400
        );
    }

    $table              = $wpdb->prefix . 'wpestate_translation_strings';
    $context_tokens     = implode( ',', array_fill( 0, count( $contexts ), '%s' ) );
    $source_rows_sql    = "SELECT context, name, value FROM {$table} WHERE language_code = %s AND context IN ({$context_tokens}) ORDER BY context ASC, name ASC";
    $source_rows_params = array_merge( array( $default_language ), $contexts );
    $source_rows        = $wpdb->get_results( $wpdb->prepare( $source_rows_sql, $source_rows_params ), ARRAY_A );

    if ( empty( $source_rows ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'No source strings were found in the default language.', 'wpr-translate' ),
            ),
            400
        );
    }

    $source_map   = array();
    $admin_values = array();

    if ( '' !== $admin_domain ) {
        $option_payload = get_option( $admin_domain, array() );

        if ( is_array( $option_payload ) ) {
            $admin_values = $option_payload;
        }
    }

    foreach ( $source_rows as $source_row ) {
        $context = isset( $source_row['context'] ) ? sanitize_text_field( (string) $source_row['context'] ) : '';
        $name    = isset( $source_row['name'] ) ? sanitize_text_field( (string) $source_row['name'] ) : '';
        $value   = isset( $source_row['value'] ) ? (string) $source_row['value'] : '';

        // For wpresidence_admin, prefer the live option value over stale table values.
        if (
            $context === $admin_domain
            && ! empty( $admin_values )
            && function_exists( 'wpr_translate_admin_get_option_value_by_path' )
        ) {
            $live_value = wpr_translate_admin_get_option_value_by_path( $admin_values, $name );

            if ( '' !== $live_value ) {
                $value = $live_value;
            }
        }

        $plain_value = trim( wp_strip_all_tags( $value ) );

        if ( '' === $context || '' === $name ) {
            continue;
        }

        // Do not include empty or numeric-only values in OpenAI payloads.
        if ( '' === $plain_value || is_numeric( $plain_value ) ) {
            continue;
        }

        $source_map[ $context . '|' . $name ] = array(
            'context' => $context,
            'name'    => $name,
            'value'   => $value,
        );
    }

    if ( empty( $source_map ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'No valid source strings were found for automatic translation.', 'wpr-translate' ),
            ),
            400
        );
    }

    $processed_count = 0;
    $updated_count   = 0;
    $inserted_count  = 0;
    $skipped_count   = 0;
    $errors          = array();
    $batch_size      = 50;
    $source_items    = array_values( $source_map );
    $batches         = array_chunk( $source_items, $batch_size );
    $batch_count     = count( $batches );
    $batch_index     = isset( $_POST['batch_index'] ) ? absint( wp_unslash( $_POST['batch_index'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing

    if ( ! isset( $batches[ $batch_index ] ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'Invalid translation batch index.', 'wpr-translate' ),
            ),
            400
        );
    }

    $batch_strings = $batches[ $batch_index ];
    $batch_payload = array(
        'mode'            => 'string_translation',
        'target_language' => $target_language,
        'strings'         => array(),
    );

    foreach ( $batch_strings as $batch_string ) {
        $batch_payload['strings'][] = array(
            'context' => $batch_string['context'],
            'name'    => $batch_string['name'],
            'value'   => $batch_string['value'],
        );
    }

    $batch_json = wp_json_encode( $batch_payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );

    if ( false === $batch_json || null === $batch_json ) {
        $errors[] = sprintf( __( 'Failed to encode batch %d payload.', 'wpr-translate' ), ( $batch_index + 1 ) );
    } else {
        $batch_response = wpr_translate_openai_translate_json_payload( $target_language, $batch_json, 'strings' );

        if ( is_wp_error( $batch_response ) ) {
            $errors[] = $batch_response->get_error_message();
        } else {
            if ( ! isset( $batch_response['strings'] ) || ! is_array( $batch_response['strings'] ) ) {
                $errors[] = sprintf( __( 'Invalid response format for batch %d.', 'wpr-translate' ), ( $batch_index + 1 ) );
            } else {
                foreach ( $batch_response['strings'] as $translated_row ) {
                    if ( ! is_array( $translated_row ) ) {
                        $skipped_count++;
                        continue;
                    }

                    $context = isset( $translated_row['context'] ) ? sanitize_text_field( (string) $translated_row['context'] ) : '';
                    $name    = isset( $translated_row['name'] ) ? sanitize_text_field( (string) $translated_row['name'] ) : '';

                    if ( '' === $context || '' === $name ) {
                        $skipped_count++;
                        continue;
                    }

                    $key = $context . '|' . $name;

                    if ( ! isset( $source_map[ $key ] ) ) {
                        $skipped_count++;
                        continue;
                    }

                    $source_value = (string) $source_map[ $key ]['value'];
                    $translation  = '';

                    if ( isset( $translated_row['translation'] ) ) {
                        $translation = (string) $translated_row['translation'];
                    } elseif ( isset( $translated_row['value'] ) ) {
                        $translation = (string) $translated_row['value'];
                    }

                    $translation = wp_kses_post( $translation );
                    $status      = '' === trim( wp_strip_all_tags( $translation ) ) ? 0 : 1;
                    $processed_count++;

                    $existing_row = $wpdb->get_row(
                        $wpdb->prepare(
                            "SELECT string_id FROM {$table} WHERE context = %s AND name = %s AND language_code = %s LIMIT 1",
                            $context,
                            $name,
                            $target_language
                        ),
                        ARRAY_A
                    );

                    if ( ! empty( $existing_row ) ) {
                        $result = $wpdb->update(
                            $table,
                            array(
                                'translation' => $translation,
                                'status'      => $status,
                                'updated_at'  => current_time( 'mysql' ),
                            ),
                            array(
                                'string_id' => (int) $existing_row['string_id'],
                            ),
                            array( '%s', '%d', '%s' ),
                            array( '%d' )
                        );

                        if ( false === $result ) {
                            $errors[] = sprintf(
                                /* translators: 1: context, 2: string name. */
                                __( 'Failed to update translation for %1$s / %2$s.', 'wpr-translate' ),
                                $context,
                                $name
                            );
                            continue;
                        }

                        $updated_count++;
                        continue;
                    }

                    $result = $wpdb->insert(
                        $table,
                        array(
                            'context'       => $context,
                            'name'          => $name,
                            'value'         => $source_value,
                            'language_code' => $target_language,
                            'translation'   => $translation,
                            'status'        => $status,
                            'md5'           => md5( $source_value ),
                            'updated_at'    => current_time( 'mysql' ),
                        ),
                        array( '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s' )
                    );

                    if ( false === $result ) {
                        $errors[] = sprintf(
                            /* translators: 1: context, 2: string name. */
                            __( 'Failed to insert translation for %1$s / %2$s.', 'wpr-translate' ),
                            $context,
                            $name
                        );
                        continue;
                    }

                    $inserted_count++;
                }
            }
        }
    }

    $has_more   = ( $batch_index + 1 ) < $batch_count;
    $next_batch = $has_more ? ( $batch_index + 1 ) : null;

    if ( ! $has_more && function_exists( 'wpr_translate_purge_wpestate_cache' ) ) {
        wpr_translate_purge_wpestate_cache();
    }

    wp_send_json_success(
        array(
            'processed_count' => $processed_count,
            'updated_count'   => $updated_count,
            'inserted_count'  => $inserted_count,
            'skipped_count'   => $skipped_count,
            'batch_count'     => $batch_count,
            'batch_index'     => $batch_index,
            'has_more'        => $has_more,
            'next_batch'      => $next_batch,
            'errors'          => $errors,
        )
    );
}

/**
 * AJAX handler to save automatic translation post type preferences.
 */
function wpr_translate_admin_ajax_save_post_type_preferences() {
    if ( ! check_ajax_referer( 'wpr_translate_save_post_types', 'nonce', false ) ) {
        // Reject invalid nonce requests.
        wp_send_json_error(
            array(
                'message' => __( 'Invalid request. Please refresh and try again.', 'wpr-translate' ),
            ),
            400
        );
    }

    if ( ! current_user_can( 'manage_options' ) ) {
        // Require admin capabilities.
        wp_send_json_error(
            array(
                'message' => __( 'You are not allowed to update these settings.', 'wpr-translate' ),
            ),
            403
        );
    }

    $active_languages = function_exists( 'wpr_translate_get_active_languages' ) ? wpr_translate_get_active_languages( false ) : array();
    $language_choices = array();

    if ( ! empty( $active_languages ) ) {
        foreach ( $active_languages as $language ) {
            if ( ! is_array( $language ) ) {
                continue;
            }

            $language_code = isset( $language['code'] ) ? sanitize_text_field( $language['code'] ) : '';
            $language_slug = isset( $language['slug'] ) ? sanitize_title( $language['slug'] ) : '';

            if ( '' !== $language_slug ) {
                $language_choices[] = strtolower( $language_slug );
            }

            if ( '' !== $language_code ) {
                $language_choices[] = strtolower( $language_code );
            }
        }
    }

    $available_post_types = get_post_types( array( 'show_ui' => true ), 'names' );
    $available_post_types = is_array( $available_post_types ) ? $available_post_types : array();
    $excluded_post_types  = function_exists( 'wpr_translate_get_auto_translation_excluded_post_types' )
        ? wpr_translate_get_auto_translation_excluded_post_types()
        : array();

    if ( ! empty( $excluded_post_types ) ) {
        $available_post_types = array_values( array_diff( $available_post_types, $excluded_post_types ) );
    }
    $submitted            = isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] ) ? wp_unslash( $_POST['post_types'] ) : array();
    $cleaned_preferences  = array();
    $target_language      = isset( $_POST['target_language'] ) ? sanitize_text_field( wp_unslash( $_POST['target_language'] ) ) : '';

    if ( '' !== $target_language ) {
        $target_language = strtolower( $target_language );

        if ( ! empty( $language_choices ) && ! in_array( $target_language, $language_choices, true ) ) {
            $target_language = '';
        }
    }

    if ( ! empty( $submitted ) ) {
        // Sanitize submitted post type preferences.
        foreach ( $submitted as $post_type_name => $preference ) {
            $post_type_name = sanitize_key( $post_type_name );

            if ( ! in_array( $post_type_name, $available_post_types, true ) ) {
                continue;
            }

            $cleaned_preferences[ $post_type_name ] = ( 'yes' === $preference ) ? 'yes' : 'no';
        }
    }

    $settings = get_option( 'wpr_translate_auto_translation', array() );
    $settings = is_array( $settings ) ? $settings : array();

    // Store the updated preferences and target language.
    $settings['post_types'] = $cleaned_preferences;
    $settings['target_language'] = $target_language;

    update_option( 'wpr_translate_auto_translation', $settings );

    wp_send_json_success(
        array(
            'message' => __( 'Preferences saved.', 'wpr-translate' ),
        )
    );
}

/**
 * Retrieve published posts for selected post types so they can be translated in bulk.
 */
function wpr_translate_admin_ajax_get_posts_for_auto_translation() {
    if ( ! check_ajax_referer( 'wpr_translate_get_auto_posts', 'nonce', false ) ) {
        // Reject invalid requests.
        wp_send_json_error(
            array(
                'message' => __( 'Invalid request. Please refresh and try again.', 'wpr-translate' ),
            ),
            400
        );
    }

    if ( ! current_user_can( 'manage_options' ) ) {
        // Require admin capabilities.
        wp_send_json_error(
            array(
                'message' => __( 'You are not allowed to update these settings.', 'wpr-translate' ),
            ),
            403
        );
    }

    if ( class_exists( 'QueryMonitor', false ) ) {
        // Stop Query Monitor collectors for this heavy AJAX action to avoid memory spikes.
        do_action( 'qm/cease' );
    }

    $active_languages = function_exists( 'wpr_translate_get_active_languages' ) ? wpr_translate_get_active_languages( false ) : array();
    $language_choices = array();

    if ( ! empty( $active_languages ) ) {
        foreach ( $active_languages as $language ) {
            if ( ! is_array( $language ) ) {
                continue;
            }

            $language_code = isset( $language['code'] ) ? sanitize_text_field( $language['code'] ) : '';
            $language_slug = isset( $language['slug'] ) ? sanitize_title( $language['slug'] ) : '';

            if ( '' !== $language_slug ) {
                $language_choices[] = strtolower( $language_slug );
            }

            if ( '' !== $language_code ) {
                $language_choices[] = strtolower( $language_code );
            }
        }
    }

    $available_post_types = get_post_types( array( 'show_ui' => true ), 'names' );
    $available_post_types = is_array( $available_post_types ) ? $available_post_types : array();
    $excluded_post_types  = function_exists( 'wpr_translate_get_auto_translation_excluded_post_types' )
        ? wpr_translate_get_auto_translation_excluded_post_types()
        : array();

    if ( ! empty( $excluded_post_types ) ) {
        $available_post_types = array_values( array_diff( $available_post_types, $excluded_post_types ) );
    }
    $submitted            = isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] ) ? wp_unslash( $_POST['post_types'] ) : array();
    $selected_post_types  = array();
    $target_language      = isset( $_POST['target_language'] ) ? sanitize_text_field( wp_unslash( $_POST['target_language'] ) ) : '';
    $batch_index          = isset( $_POST['batch_index'] ) ? absint( wp_unslash( $_POST['batch_index'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    // Keep each request small because draft creation clones meta/media/taxonomies.
    $batch_size           = 25;

    if ( '' !== $target_language ) {
        $target_language = strtolower( $target_language );
    }

    if ( '' === $target_language || ( ! empty( $language_choices ) && ! in_array( $target_language, $language_choices, true ) ) ) {
        wp_send_json_error(
            array(
                'message' => __( 'Please select a valid target language before starting the translation.', 'wpr-translate' ),
            )
        );
    }

    foreach ( $submitted as $post_type_name => $preference ) {
        $post_type_name = sanitize_key( $post_type_name );

        if ( ! in_array( $post_type_name, $available_post_types, true ) ) {
            continue;
        }

        if ( 'yes' === $preference ) {
            $selected_post_types[] = $post_type_name;
        }
    }

    $selected_post_types = array_unique( $selected_post_types );


    if ( empty( $selected_post_types ) ) {
        wp_send_json_success(
            array(
                'posts'             => array(),
                'batch_index'       => $batch_index,
                'batch_size'        => $batch_size,
                'has_more'          => false,
                'next_batch'        => null,
                'source_count'      => 0,
                'queued_count'      => 0,
                'draft_error_count' => 0,
                'draft_errors'      => array(),
            )
        );
    }

    // Resolve the default language code so we only translate from original content.
    $default_language_code = '';
    if ( function_exists( 'wpr_translation_get_active_languages' ) && function_exists( 'wpr_translation_get_default_language_code' ) ) {
        $languages             = wpr_translation_get_active_languages();
        $default_language_code = strtolower( sanitize_key( wpr_translation_get_default_language_code( $languages ) ) );
    }

    $meta_query = array(
        'relation' => 'OR',
        array(
            'key'     => 'wpr_translated_language',
            'compare' => 'NOT EXISTS',
        ),
    );

    if ( '' !== $default_language_code ) {
        $meta_query[] = array(
            'key'     => 'wpr_translated_language',
            'value'   => $default_language_code,
            'compare' => '=',
        );
    }

    // Read one extra source ID to detect whether the next batch exists.
    $source_query_args = array(
        'post_type'           => $selected_post_types,
        'post_status'         => 'publish',
        'fields'              => 'ids',
        'posts_per_page'      => ( $batch_size + 1 ),
        'offset'              => ( $batch_index * $batch_size ),
        'orderby'             => 'ID',
        'order'               => 'ASC',
        'no_found_rows'       => true,
        'ignore_sticky_posts' => true,
        'suppress_filters'    => true,
        'meta_query'          => $meta_query,
    );

    $source_query = new WP_Query( $source_query_args );
    $post_ids     = is_array( $source_query->posts ) ? $source_query->posts : array();

    $has_more  = count( $post_ids ) > $batch_size;
    $next_batch = $has_more ? ( $batch_index + 1 ) : null;

    if ( $has_more ) {
        // Keep only the current batch items.
        $post_ids = array_slice( $post_ids, 0, $batch_size );
    }

    $posts = array();

    foreach ( $post_ids as $post_id ) {
        $post_id = absint( $post_id );

        if ( ! $post_id ) {
            continue;
        }

        $post_type = get_post_type( $post_id );
        $post_type = is_string( $post_type ) ? sanitize_key( $post_type ) : '';

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

        $posts[] = array(
            'id'        => $post_id,
            'post_type' => $post_type,
            'title'     => wp_strip_all_tags( get_the_title( $post_id ) ),
        );
    }

    $translated_posts = array();
    $draft_errors     = array();

    foreach ( $posts as $post_data ) {
        $duplicate_id = wpr_translation_create_translation_draft( $post_data['id'], $target_language );

        if ( is_wp_error( $duplicate_id ) ) {
            // Keep processing other posts and report failures in the response payload.
            $draft_errors[] = array(
                'source_post_id' => (int) $post_data['id'],
                'error_code'     => $duplicate_id->get_error_code(),
                'message'        => $duplicate_id->get_error_message(),
            );

            continue;
        }

        $translated_posts[] = array(
            'id'             => $duplicate_id,
            'source_post_id' => $post_data['id'],
            'post_type'      => $post_data['post_type'],
            'title'          => wp_strip_all_tags( get_the_title( $duplicate_id ) ),
        );

    }


    $source_count      = count( $posts );
    $queued_count      = count( $translated_posts );
    $draft_error_count = count( $draft_errors );

    wp_send_json_success(
        array(
            'posts'             => $translated_posts,
            'batch_index'       => $batch_index,
            'batch_size'        => $batch_size,
            'has_more'          => $has_more,
            'next_batch'        => $next_batch,
            'source_count'      => $source_count,
            'queued_count'      => $queued_count,
            'draft_error_count' => $draft_error_count,
            'draft_errors'      => $draft_errors,
        )
    );
}

/**
 * Render taxonomy translation settings screen.
 */
function wpr_translate_admin_render_taxonomy_settings_page() {
    $taxonomies       = get_taxonomies( array( 'show_ui' => true ), 'objects' );
    $taxonomy_entries = array();

    if ( ! empty( $taxonomies ) ) {
        foreach ( $taxonomies as $taxonomy ) {
            if ( empty( $taxonomy->name ) ) {
                continue;
            }

            $taxonomy_entries[] = array(
                'name'  => $taxonomy->name,
                'label' => ( isset( $taxonomy->labels->singular_name ) && $taxonomy->labels->singular_name ) ? $taxonomy->labels->singular_name : $taxonomy->label,
            );
        }
    }

    $data = array(
        'settings'   => get_option( 'wpr_translate_settings', array() ),
        'taxonomies' => $taxonomy_entries,
    );

    wpr_translate_admin_render_view( 'settings-taxonomy', $data );
}

/**
 * Render custom field rules screen.
 */
function wpr_translate_admin_render_custom_field_rules_page() {
    $data = array(
        'custom_field_rules' => wpr_translate_get_custom_field_rules(),
        'custom_field_state' => wpr_translate_get_custom_field_rules_state(),
    );

    wpr_translate_admin_render_view( 'settings-custom-fields', $data );
}

/**
 * Build the data payload used by the menu synchronization page.
 *
 * @return array
 */
function wpr_translate_admin_prepare_menu_sync_view_data() {
    $overview      = function_exists( 'wpr_translation_get_nav_menu_overview' ) ? wpr_translation_get_nav_menu_overview() : array();
    $default_code  = isset( $overview['default'] ) ? strtolower( sanitize_key( $overview['default'] ) ) : '';
    $menu_entries  = isset( $overview['menus'] ) && is_array( $overview['menus'] ) ? $overview['menus'] : array();
    $default_menus = array();
    $other_menus   = array();

    foreach ( $menu_entries as $entry ) {
        $menu_id = isset( $entry['id'] ) ? (int) $entry['id'] : 0;

        if ( $menu_id <= 0 ) {
            continue;
        }

        $language_code = isset( $entry['language'] ) ? strtolower( sanitize_key( $entry['language'] ) ) : '';

        if ( '' === $language_code ) {
            $language_code = $default_code;
        }

        $record = array(
            'id'       => $menu_id,
            'name'     => isset( $entry['name'] ) ? sanitize_text_field( $entry['name'] ) : '',
            'language' => $language_code,
        );

        if ( $language_code === $default_code ) {
            $default_menus[ $menu_id ] = $record;
        } else {
            $other_menus[ $menu_id ] = $record;
        }
    }

    $selected_default_menu = isset( $_GET['default_menu'] ) ? absint( $_GET['default_menu'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

    if ( $selected_default_menu > 0 && ! isset( $default_menus[ $selected_default_menu ] ) ) {
        $selected_default_menu = 0;
    }

    $selected_other_menu = isset( $_GET['other_menu'] ) ? absint( $_GET['other_menu'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

    if ( $selected_other_menu > 0 && ! isset( $other_menus[ $selected_other_menu ] ) ) {
        $selected_other_menu = 0;
    }

    $fallback_link = isset( $_GET['fallback_link'] ) ? esc_url_raw( wp_unslash( $_GET['fallback_link'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

    $default_menu_items = array();
    $other_menu_items   = array();

    if ( $selected_default_menu > 0 ) {
        $default_menu_items = wpr_translate_admin_collect_menu_sync_items( $selected_default_menu );
    }

    if ( $selected_other_menu > 0 ) {
        $other_menu_items = wpr_translate_admin_collect_menu_sync_items( $selected_other_menu );
    }

    return array(
        'page_slug'             => 'wpr-translate-menu-sync',
        'default_language_code' => $default_code,
        'default_menus'         => array_values( $default_menus ),
        'other_menus'           => array_values( $other_menus ),
        'selected_default_menu' => $selected_default_menu,
        'selected_other_menu'   => $selected_other_menu,
        'default_menu_items'    => $default_menu_items,
        'other_menu_items'      => $other_menu_items,
        'fallback_link'         => $fallback_link,
    );
}

/**
 * Collect menu items formatted for the menu synchronization preview.
 *
 * @param int $menu_id Menu identifier.
 *
 * @return array<int, array>
 */
function wpr_translate_admin_collect_menu_sync_items( $menu_id ) {
    $menu_id = absint( $menu_id );

    if ( $menu_id <= 0 ) {
        return array();
    }

    $menu_items = wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'any' ) );

    if ( empty( $menu_items ) || is_wp_error( $menu_items ) ) {
        return array();
    }

    $indexed_items = array();
    $children_map  = array();

    foreach ( $menu_items as $item ) {
        if ( ! $item instanceof WP_Post ) {
            continue;
        }

        $item_id   = (int) $item->ID;
        $parent_id = (int) $item->menu_item_parent;

        $indexed_items[ $item_id ] = array(
            'id'    => $item_id,
            'title' => sanitize_text_field( $item->title ),
            'url'   => is_string( $item->url ) ? esc_url_raw( $item->url ) : '',
        );

        if ( ! isset( $children_map[ $parent_id ] ) ) {
            $children_map[ $parent_id ] = array();
        }

        $children_map[ $parent_id ][] = $item_id;
    }

    $flattened = array();

    $walk_items = function( $parent_id, $depth ) use ( &$walk_items, $children_map, $indexed_items, &$flattened ) {
        if ( empty( $children_map[ $parent_id ] ) ) {
            return;
        }

        foreach ( $children_map[ $parent_id ] as $child_id ) {
            if ( ! isset( $indexed_items[ $child_id ] ) ) {
                continue;
            }

            $node = $indexed_items[ $child_id ];

            $flattened[] = array(
                'id'    => $node['id'],
                'title' => $node['title'],
                'url'   => $node['url'],
                'depth' => max( 0, (int) $depth ),
            );

            $walk_items( $child_id, $depth + 1 );
        }
    };

    $walk_items( 0, 0 );

    return $flattened;
}

/**
 * Handle menu synchronization submissions.
 */
function wpr_translate_admin_handle_menu_sync_submission() {
    if ( empty( $_POST['wpr_menu_sync_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
        return;
    }

    $action = sanitize_key( wp_unslash( $_POST['wpr_menu_sync_action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing

    if ( 'sync' !== $action ) {
        return;
    }

    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }

    $source_menu  = isset( $_POST['default_menu'] ) ? absint( wp_unslash( $_POST['default_menu'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    $target_menu  = isset( $_POST['other_menu'] ) ? absint( wp_unslash( $_POST['other_menu'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    $fallback_raw = isset( $_POST['wpr_menu_sync_fallback'] ) ? wp_unslash( $_POST['wpr_menu_sync_fallback'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    $fallback_url = esc_url_raw( $fallback_raw );

    $redirect_args = array(
        'page' => 'wpr-translate-menu-sync',
    );

    if ( $source_menu > 0 ) {
        $redirect_args['default_menu'] = $source_menu;
    }

    if ( $target_menu > 0 ) {
        $redirect_args['other_menu'] = $target_menu;
    }

    if ( '' !== $fallback_url ) {
        $redirect_args['fallback_link'] = $fallback_url;
    }

    if ( ! isset( $_POST['wpr_menu_sync_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['wpr_menu_sync_nonce'] ) ), 'wpr_menu_sync' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
        add_settings_error( 'wpr_menu_sync', 'wpr_menu_sync_nonce', __( 'Security check failed. Please try again.', 'wpr-translate' ), 'error' );
        set_transient( 'settings_errors', get_settings_errors(), 30 );
        wp_safe_redirect( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ) );
        exit;
    }

    if ( $source_menu <= 0 || $target_menu <= 0 ) {
        add_settings_error( 'wpr_menu_sync', 'wpr_menu_sync_missing', __( 'Select both a source menu and a translated menu before synchronizing.', 'wpr-translate' ), 'error' );
    } elseif ( $source_menu === $target_menu ) {
        add_settings_error( 'wpr_menu_sync', 'wpr_menu_sync_same_menu', __( 'Select two different menus before starting the synchronization.', 'wpr-translate' ), 'error' );
    } else {
        $result = wpr_translate_admin_sync_menu_structures( $source_menu, $target_menu, $fallback_url );

        if ( is_wp_error( $result ) ) {
            add_settings_error( 'wpr_menu_sync', 'wpr_menu_sync_error', $result->get_error_message(), 'error' );
        } else {
            add_settings_error( 'wpr_menu_sync', 'wpr_menu_sync_success', __( 'Menu synchronization completed.', 'wpr-translate' ), 'updated' );
        }
    }

    set_transient( 'settings_errors', get_settings_errors(), 30 );

    wp_safe_redirect( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ) );
    exit;
}

/**
 * Synchronize two menu structures using translated content and fallbacks.
 *
 * @param int    $source_menu_id Source menu term ID.
 * @param int    $target_menu_id Target menu term ID.
 * @param string $fallback_url   Fallback URL used when translations are missing.
 *
 * @return true|WP_Error
 */
function wpr_translate_admin_sync_menu_structures( $source_menu_id, $target_menu_id, $fallback_url ) {
    $source_menu_id = absint( $source_menu_id );
    $target_menu_id = absint( $target_menu_id );
    $fallback_url   = esc_url_raw( $fallback_url );

    if ( $source_menu_id <= 0 || $target_menu_id <= 0 ) {
        return new WP_Error( 'wpr_menu_sync_invalid_menu', __( 'Invalid menu selection.', 'wpr-translate' ) );
    }

    $source_menu = wp_get_nav_menu_object( $source_menu_id );
    $target_menu = wp_get_nav_menu_object( $target_menu_id );

    if ( ! $source_menu instanceof WP_Term || ! $target_menu instanceof WP_Term ) {
        return new WP_Error( 'wpr_menu_sync_missing_menu', __( 'Unable to load the selected menus.', 'wpr-translate' ) );
    }

    $meta_key       = function_exists( 'wpr_translation_get_nav_menu_language_meta_key' ) ? wpr_translation_get_nav_menu_language_meta_key() : '';
    $target_language = '';

    if ( '' !== $meta_key ) {
        $target_language = get_term_meta( $target_menu_id, $meta_key, true );
        $target_language = strtolower( sanitize_key( $target_language ) );
    }

    if ( '' === $target_language ) {
        return new WP_Error( 'wpr_menu_sync_language', __( 'The translated menu is not associated with a language.', 'wpr-translate' ) );
    }

    $source_items = wp_get_nav_menu_items( $source_menu_id, array( 'post_status' => 'any' ) );

    if ( is_wp_error( $source_items ) || empty( $source_items ) ) {
        return new WP_Error( 'wpr_menu_sync_empty_source', __( 'The source menu has no items to synchronize.', 'wpr-translate' ) );
    }

    $target_items = wp_get_nav_menu_items( $target_menu_id, array( 'post_status' => 'any' ) );

    if ( ! is_wp_error( $target_items ) ) {
        foreach ( $target_items as $target_item ) {
            if ( $target_item instanceof WP_Post ) {
                wp_delete_post( $target_item->ID, true );
            }
        }
    }

    $parent_map   = array();
    $created_count = 0;

    foreach ( $source_items as $source_item ) {
        if ( ! $source_item instanceof WP_Post ) {
            continue;
        }

        $item_id    = isset( $source_item->ID ) ? (int) $source_item->ID : 0;
        $item_type  = isset( $source_item->type ) ? sanitize_key( $source_item->type ) : '';
        $item_object = isset( $source_item->object ) ? sanitize_key( $source_item->object ) : '';
        $parent_id  = (int) $source_item->menu_item_parent;

        $payload = wpr_translate_admin_build_synced_menu_item_payload( $source_item, $target_language, $fallback_url );

        if ( empty( $payload ) ) {
            continue;
        }

        if ( $parent_id > 0 && isset( $parent_map[ $parent_id ] ) ) {
            $payload['menu-item-parent-id'] = $parent_map[ $parent_id ];
        } else {
            $payload['menu-item-parent-id'] = 0;
        }

        $created_id = wp_update_nav_menu_item( $target_menu_id, 0, $payload );

        if ( $created_id && ! is_wp_error( $created_id ) ) {
            $parent_map[ $source_item->ID ] = $created_id;
            $created_count++;
        }
    }

    if ( 0 === $created_count ) {
        return new WP_Error( 'wpr_menu_sync_no_items', __( 'No menu items could be synchronized with the current settings.', 'wpr-translate' ) );
    }

    return true;
}

/**
 * Build the menu item payload for synchronization.
 *
 * @param WP_Post $menu_item      Source menu item post object.
 * @param string  $target_language Target language code.
 * @param string  $fallback_url    Fallback URL when translations are missing.
 *
 * @return array
 */
function wpr_translate_admin_build_synced_menu_item_payload( $menu_item, $target_language, $fallback_url ) {
    if ( ! $menu_item instanceof WP_Post ) {
        return array();
    }

    $title     = isset( $menu_item->title ) ? sanitize_text_field( $menu_item->title ) : '';
    $item_type = isset( $menu_item->type ) ? sanitize_key( $menu_item->type ) : '';
    $object    = isset( $menu_item->object ) ? sanitize_key( $menu_item->object ) : '';

    if ( 'post_type' === $item_type && $menu_item->object_id && '' !== $target_language && function_exists( 'wpr_translation_get_element_type' ) && function_exists( 'wpr_translation_get_translation_group' ) ) {
        $element_type = wpr_translation_get_element_type( $object );
        $group        = wpr_translation_get_translation_group( (int) $menu_item->object_id, $element_type );

        if ( ! empty( $group['translations'][ $target_language ] ) ) {
            $translated_id = (int) $group['translations'][ $target_language ];

            if ( wpr_translation_is_translation_post_available( $translated_id ) ) {
                $translated_title = get_the_title( $translated_id );

                if ( '' === $translated_title ) {
                    $translated_title = $title;
                }

                return array(
                    'menu-item-title'     => sanitize_text_field( $translated_title ),
                    'menu-item-object-id' => $translated_id,
                    'menu-item-object'    => $object,
                    'menu-item-type'      => 'post_type',
                    'menu-item-status'    => 'publish',
                );
            }
        }

        if ( '' !== $fallback_url ) {
            return array(
                'menu-item-title'  => $title,
                'menu-item-url'    => esc_url_raw( $fallback_url ),
                'menu-item-object' => 'custom',
                'menu-item-type'   => 'custom',
                'menu-item-status' => 'publish',
            );
        }

        return array();
    }

    if ( 'taxonomy' === $item_type && $menu_item->object_id && '' !== $object && '' !== $target_language ) {
        $translated_term_id = '';

        if ( function_exists( 'wpr_translate_lookup_translated_term_id_strict' ) ) {
            $translated_term_id = wpr_translate_lookup_translated_term_id_strict( (int) $menu_item->object_id, $object, $target_language );
        }

        if ( $translated_term_id ) {
            $localized_url = wpr_translate_admin_generate_taxonomy_menu_url( $translated_term_id, $object, $target_language );

            if ( '' !== $localized_url ) {
                return array(
                    'menu-item-title'     => $title,
                    'menu-item-object-id' => (int) $translated_term_id,
                    'menu-item-object'    => $object,
                    'menu-item-type'      => 'taxonomy',
                    'menu-item-status'    => 'publish',
                    'menu-item-url'       => $localized_url,
                );
            }
        }

        if ( '' !== $fallback_url ) {
            return array(
                'menu-item-title'  => $title,
                'menu-item-url'    => esc_url_raw( $fallback_url ),
                'menu-item-object' => 'custom',
                'menu-item-type'   => 'custom',
                'menu-item-status' => 'publish',
            );
        }

        return array();
    }

    if ( 'custom' === $item_type ) {
        $url = isset( $menu_item->url ) ? esc_url_raw( $menu_item->url ) : '';

        if ( '' === $url ) {
            return array();
        }

        return array(
            'menu-item-title'  => $title,
            'menu-item-url'    => $url,
            'menu-item-object' => 'custom',
            'menu-item-type'   => 'custom',
            'menu-item-status' => 'publish',
        );
    }

    return array();
}

/**
 * Generate a localized taxonomy URL for the provided language.
 *
 * @param int    $term_id        Translated term identifier.
 * @param string $taxonomy       Taxonomy slug.
 * @param string $language_code  Target language code.
 *
 * @return string
 */
function wpr_translate_admin_generate_taxonomy_menu_url( $term_id, $taxonomy, $language_code ) {
    $term_id  = absint( $term_id );
    $taxonomy = sanitize_key( $taxonomy );
    $language = is_string( $language_code ) ? trim( $language_code ) : '';

    if ( ! $term_id || '' === $taxonomy ) {
        return '';
    }

    if ( ! taxonomy_exists( $taxonomy ) ) {
        return '';
    }

    $term = get_term( $term_id, $taxonomy );

    if ( ! $term instanceof WP_Term ) {
        return '';
    }

    $url = get_term_link( $term, $taxonomy );

    if ( is_wp_error( $url ) || '' === $url ) {
        return '';
    }

    if ( function_exists( 'wpr_translation_localize_url_for_language' ) ) {
        $language_payload = wpr_translate_get_language( $language );

        if ( ! is_array( $language_payload ) ) {
            $language_payload = array(
                'code'       => $language,
                'slug'       => '',
                'is_default' => false,
            );
        }

        $language_payload = wpr_translate_normalize_language( $language_payload );
        $url              = wpr_translation_localize_url_for_language( $url, $language_payload );
    }

    return esc_url_raw( $url );
}

/**
 * Handle custom field rule management actions.
 */
function wpr_translate_admin_handle_custom_field_rules_actions() {
    if ( empty( $_POST['wpr_cf_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
        return;
    }

    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }

    $action = sanitize_key( wp_unslash( $_POST['wpr_cf_action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing

    if ( 'reload' === $action ) {
        if ( ! isset( $_POST['wpr_cf_reload_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['wpr_cf_reload_nonce'] ) ), 'wpr_cf_reload' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
            return;
        }

        if ( function_exists( 'wpr_translate_reload_custom_field_rules_from_file' ) ) {
            $result = wpr_translate_reload_custom_field_rules_from_file();
        } else {
            $result = new WP_Error( 'wpr_cf_reload_removed', __( 'Custom field reload function is not available.', 'wpr-translate' ) );
        }

        if ( is_wp_error( $result ) ) {
            add_settings_error( 'wpr_cf_preferences', 'wpr_cf_reload_error', $result->get_error_message(), 'error' );
        } else {
            add_settings_error( 'wpr_cf_preferences', 'wpr_cf_reload_success', __( 'Custom field rules reloaded from file.', 'wpr-translate' ), 'updated' );
        }
    } elseif ( 'save_overrides' === $action ) {
        if ( ! isset( $_POST['wpr_cf_overrides_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['wpr_cf_overrides_nonce'] ) ), 'wpr_cf_save_overrides' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
            return;
        }

        $submitted = array();

        if ( isset( $_POST['wpr_cf_overrides'] ) && is_array( $_POST['wpr_cf_overrides'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
            $submitted = wp_unslash( $_POST['wpr_cf_overrides'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
        }

        $sanitized = array();
        $existing  = wpr_translate_get_custom_field_overrides();

        foreach ( $existing as $key => $action_value ) {
            $sanitized[ $key ] = $action_value;
        }

        foreach ( $submitted as $key => $value ) {
            // Keep the raw identifier intact while sanitizing unsafe characters.
            $rule_key = sanitize_text_field( $key );
            $action_value = is_string( $value ) ? sanitize_key( $value ) : '';

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

            if ( '' === $action_value ) {
                if ( isset( $sanitized[ $rule_key ] ) ) {
                    unset( $sanitized[ $rule_key ] );
                }

                continue;
            }

            $sanitized[ $rule_key ] = $action_value;
        }

        wpr_translate_save_custom_field_overrides( $sanitized );

        add_settings_error( 'wpr_cf_preferences', 'wpr_cf_overrides_saved', __( 'Custom field overrides saved.', 'wpr-translate' ), 'updated' );
    } else {
        return;
    }

    set_transient( 'settings_errors', get_settings_errors(), 30 );

    $redirect = add_query_arg(
        array(
            'page'              => 'wpr-translate-custom-fields',
            'settings-updated'  => 'true',
        ),
        admin_url( 'admin.php' )
    );

    wp_safe_redirect( $redirect );
    exit;
}

/**
 * Persist a custom field rule override via AJAX.
 */
function wpr_translate_admin_ajax_save_custom_field_rule() {
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error(
            array( 'message' => __( 'You are not allowed to manage custom field rules.', 'wpr-translate' ) ),
            403
        );
    }

    check_ajax_referer( 'wpr_translate_save_cf_rule', 'nonce' );

    $rule_id     = isset( $_POST['rule'] ) ? sanitize_text_field( wp_unslash( $_POST['rule'] ) ) : '';
    $action_raw  = isset( $_POST['value'] ) ? wp_unslash( $_POST['value'] ) : '';
    $action_value = sanitize_key( $action_raw );

    if ( '' === $rule_id ) {
        wp_send_json_error( array( 'message' => __( 'Missing custom field identifier.', 'wpr-translate' ) ) );
    }

    $allowed_actions = array( 'copy', 'translate', 'copy-once', 'ignore' );

    if ( '' === $action_value || ! in_array( $action_value, $allowed_actions, true ) ) {
        wp_send_json_error( array( 'message' => __( 'Invalid action provided.', 'wpr-translate' ) ) );
    }

    $rules = wpr_translate_get_custom_field_rules();

    if ( ! isset( $rules[ $rule_id ] ) ) {
        wp_send_json_error( array( 'message' => __( 'Unknown custom field rule.', 'wpr-translate' ) ) );
    }

    $file_action = isset( $rules[ $rule_id ]['file_action'] ) ? $rules[ $rule_id ]['file_action'] : '';
    $overrides   = wpr_translate_get_custom_field_overrides();
    $message     = __( 'Action updated.', 'wpr-translate' );
    $source      = 'override';

    if ( '' !== $file_action && $file_action === $action_value ) {
        if ( isset( $overrides[ $rule_id ] ) ) {
            unset( $overrides[ $rule_id ] );
        }
        $message = __( 'Reverted to default action.', 'wpr-translate' );
        $source  = 'file';
    } else {
        $overrides[ $rule_id ] = $action_value;
    }

    wpr_translate_save_custom_field_overrides( $overrides );

    wp_send_json_success(
        array(
            'rule'    => $rule_id,
            'action'  => ( 'file' === $source && '' !== $file_action ) ? $file_action : $action_value,
            'source'  => $source,
            'message' => $message,
        )
    );
}

/**
 * Build dashboard statistics.
 *
 * @return array
 */
/**
 * Helper to load a view file.
 *
 * @param string $view View identifier.
 * @param array  $data View data payload.
 */
function wpr_translate_admin_render_view( $view, $data = array() ) {
    $view_file = WPR_TRANSLATE_PATH . 'includes/admin/views/' . $view . '.php';

    if ( ! file_exists( $view_file ) ) {
        return;
    }

    $data = apply_filters( 'wpr_translate_view_data', $data, $view );

    include $view_file;
}

