<?php
/**
 * Taxonomy translation orchestration layer.
 *
 * This file manages all logic related to cloning taxonomy terms across
 * languages, syncing their hierarchy, and filtering term queries according to
 * translation preferences. It interacts heavily with the translation database
 * tables and WordPress taxonomy APIs to keep translated term sets in sync.
 */

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

/**
 * Retrieve user-defined translation modes for each taxonomy.
 *
 * @return array<string, string> Map of taxonomy slug to mode.
 */
function wpr_translation_get_taxonomy_modes_settings() {
    // Read taxonomy settings from the plugin options.
    $settings = get_option( 'wpr_translate_settings', array() );

    $modes = array();

    if ( isset( $settings['taxonomy_modes'] ) && is_array( $settings['taxonomy_modes'] ) ) {
        foreach ( $settings['taxonomy_modes'] as $taxonomy => $mode ) {
            $taxonomy = sanitize_key( $taxonomy );

            if ( '' === $taxonomy ) {
                // Skip invalid taxonomy keys.
                continue;
            }

            $mode = sanitize_key( $mode );

            if ( ! in_array( $mode, array( 'translate', 'not_translatable' ), true ) ) {
                // Default to translate mode when invalid.
                $mode = 'translate';
            }

            $modes[ $taxonomy ] = $mode;
        }
    }

    return $modes;
}

/**
 * Determine the translation mode of a specific taxonomy.
 *
 * @param string $taxonomy Taxonomy slug.
 *
 * @return string Mode name.
 */
function wpr_translation_get_taxonomy_mode( $taxonomy ) {
    $taxonomy = sanitize_key( $taxonomy );

    if ( '' === $taxonomy ) {
        // Default to translate when taxonomy is missing.
        return 'translate';
    }

    $modes = wpr_translation_get_taxonomy_modes_settings();

    return isset( $modes[ $taxonomy ] ) ? $modes[ $taxonomy ] : 'translate';
}

/**
 * Fetch taxonomies configured for automatic term duplication.
 *
 * @return array<string, bool> Map of taxonomy slug to duplication flag.
 */
function wpr_translation_get_taxonomy_auto_duplicate_settings() {
    // Read taxonomy auto-duplicate settings from options.
    $options = get_option( 'wpr_translate_settings', array() );

    $settings = array();

    if ( isset( $options['taxonomy_auto_duplicate'] ) && is_array( $options['taxonomy_auto_duplicate'] ) ) {
        foreach ( $options['taxonomy_auto_duplicate'] as $taxonomy => $enabled ) {
            $taxonomy = sanitize_key( $taxonomy );

            if ( '' === $taxonomy ) {
                // Skip invalid taxonomy keys.
                continue;
            }

            $settings[ $taxonomy ] = ! empty( $enabled );
        }
    }

    return $settings;
}

/**
 * Check whether auto-duplication is turned on for a taxonomy.
 *
 * @param string $taxonomy Taxonomy slug.
 *
 * @return bool True when auto-duplication should run.
 */
function wpr_translation_is_taxonomy_auto_duplicate_enabled( $taxonomy ) {
    $taxonomy = sanitize_key( $taxonomy );

    if ( '' === $taxonomy ) {
        // Empty taxonomy cannot be auto-duplicated.
        return false;
    }

    $settings = wpr_translation_get_taxonomy_auto_duplicate_settings();

    return ! empty( $settings[ $taxonomy ] );
}

/**
 * Determine if a taxonomy participates in translation.
 *
 * @param string $taxonomy Taxonomy slug.
 *
 * @return bool True when translations are allowed.
 */
function wpr_translation_is_taxonomy_translatable( $taxonomy ) {
    return 'not_translatable' !== wpr_translation_get_taxonomy_mode( $taxonomy );
}

/**
 * Check whether untranslated terms are hidden from front-end queries.
 *
 * @param string $taxonomy Taxonomy slug.
 *
 * @return bool True when untranslated terms should not render.
 */
function wpr_translation_should_hide_untranslated_terms( $taxonomy ) {
    return wpr_translation_is_taxonomy_translatable( $taxonomy );
}

/**
 * Generate a nonce action string tied to a specific term translation request.
 *
 * @param int    $term_id  Term identifier.
 * @param string $taxonomy Taxonomy slug.
 * @param string $language Target language code.
 *
 * @return string Nonce action value.
 */
function wpr_translation_get_term_translation_nonce_action( $term_id, $taxonomy, $language ) {
    return 'wpr_add_term_translation_' . absint( $term_id ) . '_' . sanitize_key( $taxonomy ) . '_' . sanitize_key( $language );
}

/**
 * Copy meta data from a source term to a target term.
 *
 * @param int $source_term_id Source term ID.
 * @param int $target_term_id Target term ID.
 *
 * @return void
 */
function wpr_translation_duplicate_term_meta( $source_term_id, $target_term_id ) {
    $source_term_id = absint( $source_term_id );
    $target_term_id = absint( $target_term_id );

    if ( $source_term_id <= 0 || $target_term_id <= 0 || $source_term_id === $target_term_id ) {
        return;
    }

    // Load all metadata from the source term.
    $meta = get_term_meta( $source_term_id );

    if ( empty( $meta ) ) {
        $meta = array();
    }

    foreach ( $meta as $meta_key => $values ) {
        // Reset target meta before copying values.
        delete_term_meta( $target_term_id, $meta_key );

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

        foreach ( $values as $value ) {
            // Copy each meta value to the target term.
            add_term_meta( $target_term_id, $meta_key, maybe_unserialize( $value ) );
        }
    }

    // Copy legacy WP Residence term option payload used by taxonomy UI tabs.
    $source_option = get_option( 'taxonomy_' . $source_term_id, array() );

    if ( is_array( $source_option ) ) {
        // Category tagline is text and should be translated manually/automatically.
        $source_option['category_tagline'] = '';
        update_option( 'taxonomy_' . $target_term_id, $source_option );
    }
}

/**
 * Duplicate a taxonomy term into a target language and register translation metadata.
 *
 * @param int    $source_term_id  Source term ID.
 * @param string $taxonomy        Taxonomy slug.
 * @param string $target_language Target language code.
 * @param array  $args            Optional overrides (trid, source_language).
 *
 * @return array{term_id:int,created:bool}|WP_Error Operation details or error.
 */
function wpr_translation_duplicate_term_for_language( $source_term_id, $taxonomy, $target_language, $args = array() ) {
    // Sanitize inputs for safe processing.
    $source_term_id  = absint( $source_term_id );
    $taxonomy        = sanitize_key( $taxonomy );
    $target_language = strtolower( sanitize_key( $target_language ) );

    if ( $source_term_id <= 0 || '' === $taxonomy || '' === $target_language ) {
        return new WP_Error( 'wpr_translate_invalid_term_duplication', __( 'Invalid term duplication request.', 'wpr-translate' ) );
    }

    if ( ! wpr_translation_is_taxonomy_translatable( $taxonomy ) ) {
        // Prevent duplication for non-translatable taxonomies.
        return new WP_Error( 'wpr_translate_taxonomy_not_translatable', __( 'This taxonomy cannot be translated.', 'wpr-translate' ) );
    }

    if ( function_exists( 'wpr_translate_resolve_original_term_id' ) ) {
        $resolved_source_term_id = absint( wpr_translate_resolve_original_term_id( $source_term_id, $taxonomy ) );

        if ( $resolved_source_term_id > 0 ) {
            $source_term_id = $resolved_source_term_id;
        }
    }

    $meta_mapped_term_id = 0;

    if ( function_exists( 'wpr_translate_lookup_translated_term_id_strict' ) ) {
        $strict_mapped_term = wpr_translate_lookup_translated_term_id_strict( $source_term_id, $taxonomy, $target_language );

        if ( is_numeric( $strict_mapped_term ) ) {
            $strict_mapped_term = absint( $strict_mapped_term );

            if ( $strict_mapped_term > 0 && $strict_mapped_term !== $source_term_id ) {
                $meta_mapped_term_id = $strict_mapped_term;
            }
        }
    }

    if ( $meta_mapped_term_id > 0 ) {
        if ( function_exists( 'wpr_translate_link_term_translation_meta' ) ) {
            wpr_translate_link_term_translation_meta( $source_term_id, $meta_mapped_term_id, $target_language, $taxonomy );
        }

        return array(
            'term_id' => $meta_mapped_term_id,
            'created' => false,
        );
    }

    $defaults = array(
        'trid'            => 0,
        'source_language' => '',
    );

    $args = wp_parse_args( $args, $defaults );

    $element_type = wpr_translation_get_taxonomy_element_type( $taxonomy );
    $group        = wpr_translation_get_translation_group( $source_term_id, $element_type );
    $translations = isset( $group['translations'] ) && is_array( $group['translations'] ) ? $group['translations'] : array();

    if ( isset( $translations[ $target_language ] ) && $translations[ $target_language ] ) {
        // Bail out early when the requested translation already exists to avoid duplicates.
        $existing_term_id = absint( $translations[ $target_language ] );

        if ( $existing_term_id > 0 && $existing_term_id !== $source_term_id && function_exists( 'wpr_translate_link_term_translation_meta' ) ) {
            wpr_translate_link_term_translation_meta( $source_term_id, $existing_term_id, $target_language, $taxonomy );
        }

        return array(
            'term_id' => $existing_term_id,
            'created' => false,
        );
    }

    if ( empty( $args['trid'] ) && ! empty( $group['trid'] ) ) {
        $args['trid'] = (int) $group['trid'];
    }

    if ( '' === $args['source_language'] ) {
        // Derive the source language by matching the source term within the translation group.
        foreach ( $translations as $code => $translated_term_id ) {
            if ( (int) $translated_term_id === $source_term_id ) {
                $args['source_language'] = sanitize_key( $code );
                break;
            }
        }
    }

    if ( '' === $args['source_language'] && ! empty( $group['source_language'] ) ) {
        // Use the stored source language from the translation group.
        $args['source_language'] = sanitize_key( $group['source_language'] );
    }

    $languages        = wpr_translation_get_active_languages();
    $default_language = wpr_translation_get_default_language_code( $languages );

    if ( '' === $args['source_language'] ) {
        // Fall back to default language when no source language is resolved.
        $args['source_language'] = $default_language;
    }

    $source_term = get_term( $source_term_id, $taxonomy );

    if ( ! $source_term || is_wp_error( $source_term ) ) {
        // Abort if the source term cannot be loaded.
        return new WP_Error( 'wpr_translate_source_term_missing', __( 'Source term not found.', 'wpr-translate' ) );
    }

    $parent = 0;

    if ( ! empty( $source_term->parent ) ) {
        // Resolve parent mapping using shared meta-first term lookup helpers.
        $canonical_parent_id = (int) $source_term->parent;

        if ( function_exists( 'wpr_translate_resolve_original_term_id' ) ) {
            $resolved_parent = absint( wpr_translate_resolve_original_term_id( $canonical_parent_id, $taxonomy ) );

            if ( $resolved_parent > 0 ) {
                $canonical_parent_id = $resolved_parent;
            }
        }

        $parent = $canonical_parent_id;

        if ( function_exists( 'wpr_translate_lookup_translated_term_id_strict' ) ) {
            $translated_parent = wpr_translate_lookup_translated_term_id_strict( $canonical_parent_id, $taxonomy, $target_language );

            if ( is_numeric( $translated_parent ) ) {
                $translated_parent = absint( $translated_parent );

                if ( $translated_parent > 0 ) {
                    $parent = $translated_parent;
                }
            }
        }
    }

    $source_slug = $source_term->slug;

    if ( '' === $source_slug ) {
        // Use the term name when the slug is missing.
        $source_slug = $source_term->name;
    }

    $unique_slug = wp_unique_term_slug(
        sanitize_title( $source_slug ),
        (object) array(
            'term_id'  => 0,
            'taxonomy' => $taxonomy,
            'parent'   => $parent,
        )
    );

    // Set a guard flag to prevent recursive term creation hooks.
    $previous_guard                                    = ! empty( $GLOBALS['wpr_translation_creating_term_translation'] );
    $GLOBALS['wpr_translation_creating_term_translation'] = true;

    $result = wp_insert_term(
        $source_term->name,
        $taxonomy,
        array(
            'slug'        => $unique_slug,
            // Keep description empty so it can be translated for the target language.
            'description' => '',
            'parent'      => $parent,
        )
    );

    if ( $previous_guard ) {
        $GLOBALS['wpr_translation_creating_term_translation'] = true;
    } else {
        unset( $GLOBALS['wpr_translation_creating_term_translation'] );
    }

    $new_term_id      = 0;
    $created_new_term = true;

    if ( is_wp_error( $result ) ) {
        if ( 'term_exists' === $result->get_error_code() ) {
            $existing_term_id = (int) $result->get_error_data();

            if ( $existing_term_id > 0 ) {
                $new_term_id      = $existing_term_id;
                $created_new_term = false;
            }
        }

        if ( 0 === $new_term_id ) {
            return $result;
        }
    } else {
        $new_term_id = (int) $result['term_id'];
    }

    if ( $created_new_term ) {
        // Copy term metadata only for newly created terms.
        wpr_translation_duplicate_term_meta( $source_term_id, $new_term_id );
    }

    if ( $new_term_id > 0 && $new_term_id !== $source_term_id && function_exists( 'wpr_translate_link_term_translation_meta' ) ) {
        wpr_translate_link_term_translation_meta( $source_term_id, $new_term_id, $target_language, $taxonomy );
    }

    if ( $args['trid'] <= 0 && function_exists( 'wpr_translation_generate_trid' ) ) {
        // Create a translation group ID when needed.
        $args['trid'] = wpr_translation_generate_trid();
    }

    if ( $args['trid'] > 0 ) {
        $source_language = $args['source_language'];

        if ( '' === $source_language || $source_language === $target_language ) {
            $source_language = ( '' !== $default_language && $default_language !== $target_language ) ? $default_language : '';
        }

        wpr_translation_insert_translation_record(
            $new_term_id,
            $element_type,
            $args['trid'],
            $target_language,
            $source_language ? $source_language : null,
            'publish',
            false
        );
    }

    return array(
        'term_id' => $new_term_id,
        'created' => $created_new_term,
    );
}
/**
 * Register translation metadata whenever a new term is created.
 *
 * @param int    $term_id  Term ID.
 * @param int    $tt_id    Term taxonomy ID (unused).
 * @param string $taxonomy Taxonomy slug.
 *
 * @return void
 */
function wpr_translation_handle_created_term( $term_id, $tt_id, $taxonomy ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
    // Normalize inputs and bail when invalid.
    $term_id  = absint( $term_id );
    $taxonomy = sanitize_key( $taxonomy );
    if ( $term_id <= 0 || '' === $taxonomy || ! empty( $GLOBALS['wpr_translation_creating_term_translation'] ) ) {
        return;
    }

    if ( ! wpr_translation_is_taxonomy_translatable( $taxonomy ) ) {
        // Skip non-translatable taxonomies.
        return;
    }
    $languages = wpr_translation_get_active_languages();
    if ( empty( $languages ) ) {
        // No active languages means nothing to store.
        return;
    }
    $element_type = wpr_translation_get_taxonomy_element_type( $taxonomy );
    $group        = wpr_translation_get_translation_group( $term_id, $element_type );
    if ( ! empty( $group['trid'] ) ) {
        // Existing translation group already recorded.
        return;
    }
    // Prefer the language selected in the creation form when available.
    $language_code = isset( $_POST['wpr_translation_term_language'] ) ? sanitize_key( wp_unslash( $_POST['wpr_translation_term_language'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    if ( '' === $language_code ) {
        // Fall back to the default language when none supplied.
        $language_code = wpr_translation_get_default_language_code( $languages );
    }
    if ( '' === $language_code ) {
        return;
    }
    $trid = function_exists( 'wpr_translation_generate_trid' ) ? wpr_translation_generate_trid() : 0;
    if ( ! $trid ) {
        // Abort when a translation group cannot be created.
        return;
    }
    $default_language = wpr_translation_get_default_language_code( $languages );
    $is_original      = ( '' !== $default_language && $language_code === $default_language );
    wpr_translation_insert_translation_record(
        $term_id,
        $element_type,
        $trid,
        $language_code,
        $is_original ? null : $default_language,
        'publish',
        $is_original
    );

    if ( wpr_translation_is_taxonomy_auto_duplicate_enabled( $taxonomy ) ) {
        foreach ( $languages as $language ) {
            $code = isset( $language['code'] ) ? sanitize_key( $language['code'] ) : '';

            if ( '' === $code || $code === $language_code ) {
                continue;
            }

            // Automatically seed translations for all remaining languages.
            wpr_translation_duplicate_term_for_language(
                $term_id,
                $taxonomy,
                $code,
                array(
                    'trid'            => $trid,
                    'source_language' => $language_code,
                )
            );
        }
    }
}
add_action( 'created_term', 'wpr_translation_handle_created_term', 20, 3 );
/**
 * Persist language assignments and update translation group when a term is edited.
 *
 * @param int    $term_id  Term ID.
 * @param int    $tt_id    Term taxonomy ID (unused).
 * @param string $taxonomy Taxonomy slug.
 *
 * @return void
 */
function wpr_translation_handle_edited_term( $term_id, $tt_id, $taxonomy ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
    // Normalize inputs for safety.
    $term_id  = absint( $term_id );
    $taxonomy = sanitize_key( $taxonomy );
    if ( $term_id <= 0 || '' === $taxonomy ) {
        return;
    }

    if ( ! wpr_translation_is_taxonomy_translatable( $taxonomy ) ) {
        // Skip edits for non-translatable taxonomies.
        return;
    }

    if ( ! empty( $GLOBALS['wpr_translation_syncing_term_parents'] ) ) {
        // Avoid recursive updates when syncing parents.
        return;
    }
    $taxonomy_obj = get_taxonomy( $taxonomy );
    if ( ! $taxonomy_obj || ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
        return;
    }
    $languages = wpr_translation_get_active_languages();
    if ( empty( $languages ) ) {
        return;
    }
    $element_type = wpr_translation_get_taxonomy_element_type( $taxonomy );
    $nonce        = isset( $_POST['wpr_translation_term_language_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['wpr_translation_term_language_nonce'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
    if ( $nonce && wp_verify_nonce( $nonce, 'wpr_translation_term_language_' . $term_id ) ) {
        // Trust the submitted language when the nonce validates.
        $language_code  = isset( $_POST['wpr_translation_term_language'] ) ? sanitize_key( wp_unslash( $_POST['wpr_translation_term_language'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $source_term    = isset( $_POST['wpr_translation_term_source'] ) ? absint( $_POST['wpr_translation_term_source'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
        $language_codes = wp_list_pluck( $languages, 'code' );
        if ( ! in_array( $language_code, $language_codes, true ) ) {
            $language_code = wpr_translation_get_default_language_code( $languages );
        }
        if ( '' === $language_code ) {
            return;
        }
        $group        = wpr_translation_get_translation_group( $term_id, $element_type );
        $translations = is_array( $group['translations'] ) ? $group['translations'] : array();
        $trid         = ! empty( $group['trid'] ) ? (int) $group['trid'] : 0;
        if ( ! $trid && function_exists( 'wpr_translation_generate_trid' ) ) {
            // Generate a new translation group when missing.
            $trid = wpr_translation_generate_trid();
        }
        if ( ! $trid ) {
            // Abort when no group can be created.
            return;
        }
        $default_language = wpr_translation_get_default_language_code( $languages );
        $is_original      = ( '' !== $default_language && $language_code === $default_language );
        $source_language  = '';
        if ( $source_term && ! empty( $translations ) ) {
            // Map the selected source translation back to its language code.
            foreach ( $translations as $code => $translated_term_id ) {
                if ( (int) $translated_term_id === $source_term ) {
                    $source_language = sanitize_key( $code );
                    break;
                }
            }
        }
        if ( '' === $source_language && ! empty( $group['source_language'] ) ) {
            $source_language = sanitize_key( $group['source_language'] );
        }
        if ( '' === $source_language && '' !== $default_language && ! $is_original ) {
            // Use default language as source when appropriate.
            $source_language = $default_language;
        }
        wpr_translation_insert_translation_record(
            $term_id,
            $element_type,
            $trid,
            $language_code,
            $is_original ? null : $source_language,
            'publish',
            $is_original
        );
        wpr_translation_mark_term_translations_needing_update( $trid, $element_type, $term_id );
        wpr_translation_sync_term_translation_hierarchy( $term_id, $taxonomy );
        return;
    }
    $group = wpr_translation_get_translation_group( $term_id, $element_type );
    if ( empty( $group['trid'] ) ) {
        // Nothing to update when no group exists.
        return;
    }
    wpr_translation_mark_single_term_current( $term_id, $element_type );
    wpr_translation_mark_term_translations_needing_update( (int) $group['trid'], $element_type, $term_id );
    wpr_translation_sync_term_translation_hierarchy( $term_id, $taxonomy );
}
add_action( 'edited_term', 'wpr_translation_handle_edited_term', 20, 3 );
/**
 * Flag a term translation as up-to-date for the current translator.
 *
 * @param int    $term_id      Term identifier.
 * @param string $element_type Translation element type string.
 *
 * @return void
 */
function wpr_translation_mark_single_term_current( $term_id, $element_type ) {
    global $wpdb;
    // Normalize inputs.
    $term_id      = absint( $term_id );
    $element_type = sanitize_key( $element_type );
    if ( $term_id <= 0 || '' === $element_type ) {
        return;
    }
    // Update the translation record to mark it current.
    $table = $wpdb->prefix . 'wpestate_translation_translations';
    $wpdb->update(
        $table,
        array(
            'needs_update' => 0,
            'translator_id' => get_current_user_id(),
        ),
        array(
            'element_id'   => $term_id,
            'element_type' => $element_type,
        ),
        array( '%d', '%d' ),
        array( '%d', '%s' )
    );
}
/**
 * Mark translations in a group as needing an update when one term changes.
 *
 * @param int    $trid         Translation group ID.
 * @param string $element_type Element type string.
 * @param int    $term_id      Term identifier triggering the update.
 *
 * @return void
 */
function wpr_translation_mark_term_translations_needing_update( $trid, $element_type, $term_id ) {
    global $wpdb;

    // Normalize inputs.
    $trid         = absint( $trid );
    $term_id      = absint( $term_id );
    $element_type = sanitize_key( $element_type );

    if ( $trid <= 0 || $term_id <= 0 || '' === $element_type ) {
        return;
    }

    $table = $wpdb->prefix . 'wpestate_translation_translations';

    // Flag other translations in the group as needing updates.
    $wpdb->query(
        $wpdb->prepare(
            "UPDATE {$table} SET needs_update = 1 WHERE trid = %d AND element_type = %s AND element_id <> %d",
            $trid,
            $element_type,
            $term_id
        )
    );
}
/**
 * Remove translation metadata when a term is deleted.
 *
 * @param int    $term_id      Term ID.
 * @param int    $tt_id        Term taxonomy ID.
 * @param string $taxonomy     Taxonomy slug.
 * @param mixed  $deleted_term Deleted term object (unused).
 * @param mixed  $object_ids   Related objects (unused).
 *
 * @return void
 */
function wpr_translation_handle_deleted_term( $term_id, $tt_id, $taxonomy, $deleted_term, $object_ids ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
    global $wpdb;

    // Normalize inputs.
    $term_id  = absint( $term_id );
    $taxonomy = sanitize_key( $taxonomy );

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

    if ( ! wpr_translation_is_taxonomy_translatable( $taxonomy ) ) {
        // Skip non-translatable taxonomies.
        return;
    }

    $element_type = wpr_translation_get_taxonomy_element_type( $taxonomy );
    $table        = $wpdb->prefix . 'wpestate_translation_translations';

    // Load translation metadata for the deleted term.
    $record = $wpdb->get_row(
        $wpdb->prepare(
            "SELECT trid, original FROM {$table} WHERE element_id = %d AND element_type = %s LIMIT 1",
            $term_id,
            $element_type
        ),
        ARRAY_A
    );

    if ( ! $record ) {
        return;
    }

    // Remove the translation mapping tied to the deleted term ID.
    $wpdb->delete(
        $table,
        array(
            'element_id'   => $term_id,
            'element_type' => $element_type,
        ),
        array( '%d', '%s' )
    );

    if ( empty( $record['original'] ) ) {
        // Only promote replacements when the original term is deleted.
        return;
    }

    // Promote another translation to the original slot to keep the group intact.
    // Select a replacement translation to mark as original.
    $replacement = $wpdb->get_row(
        $wpdb->prepare(
            "SELECT translation_id FROM {$table} WHERE trid = %d AND element_type = %s LIMIT 1",
            (int) $record['trid'],
            $element_type
        ),
        ARRAY_A
    );

    if ( ! $replacement ) {
        return;
    }

    $wpdb->update(
        $table,
        array(
            'original'             => 1,
            'source_language_code' => null,
        ),
        array( 'translation_id' => (int) $replacement['translation_id'] ),
        array( '%d', '%s' ),
        array( '%d' )
    );
}
add_action( 'delete_term', 'wpr_translation_handle_deleted_term', 20, 5 );

/**
 * Ensure translated terms mirror the parent hierarchy of the source term.
 *
 * @param int    $term_id  Term identifier.
 * @param string $taxonomy Taxonomy slug.
 *
 * @return void
 */
function wpr_translation_sync_term_translation_hierarchy( $term_id, $taxonomy ) {
    // Normalize inputs.
    $term_id  = absint( $term_id );
    $taxonomy = sanitize_key( $taxonomy );

    if ( $term_id <= 0 || '' === $taxonomy || ! empty( $GLOBALS['wpr_translation_syncing_term_parents'] ) ) {
        return;
    }

    $taxonomy_obj = get_taxonomy( $taxonomy );

    if ( ! $taxonomy_obj || empty( $taxonomy_obj->hierarchical ) ) {
        // Only sync hierarchical taxonomies.
        return;
    }

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

    if ( ! $term || is_wp_error( $term ) ) {
        // Abort if the term cannot be loaded.
        return;
    }

    $element_type = wpr_translation_get_taxonomy_element_type( $taxonomy );
    $group        = wpr_translation_get_translation_group( $term_id, $element_type );

    if ( empty( $group['trid'] ) || empty( $group['translations'] ) || ! is_array( $group['translations'] ) ) {
        // No translations to sync.
        return;
    }

    $current_language = '';

    foreach ( $group['translations'] as $language_code => $translated_term_id ) {
        if ( (int) $translated_term_id === $term_id ) {
            $current_language = sanitize_key( $language_code );
            break;
        }
    }

    $parent_id             = (int) $term->parent;
    $parent_translations   = array();
    $parent_source_lang    = '';
    $current_language_term = 0;

    if ( $parent_id > 0 ) {
        // Load the translation group for the parent so child translations can mirror it.
        $parent_group = wpr_translation_get_translation_group( $parent_id, $element_type );

        if ( ! empty( $parent_group['translations'] ) && is_array( $parent_group['translations'] ) ) {
            foreach ( $parent_group['translations'] as $language_code => $translated_parent_id ) {
                $parent_translations[ sanitize_key( $language_code ) ] = (int) $translated_parent_id;
            }
        }

        if ( ! empty( $parent_group['source_language'] ) ) {
            $parent_source_lang = sanitize_key( $parent_group['source_language'] );
        }
    }

    if ( '' !== $current_language && isset( $parent_translations[ $current_language ] ) ) {
        $current_language_term = (int) $parent_translations[ $current_language ];
    }

    foreach ( $group['translations'] as $language_code => $translated_term_id ) {
        $language_code     = sanitize_key( $language_code );
        $translated_term_id = (int) $translated_term_id;

        if ( $translated_term_id <= 0 || $translated_term_id === $term_id ) {
            continue;
        }

        $target_parent = 0;

        if ( $parent_id > 0 ) {
            if ( isset( $parent_translations[ $language_code ] ) && $parent_translations[ $language_code ] > 0 ) {
                $target_parent = (int) $parent_translations[ $language_code ];
            } elseif ( $current_language_term > 0 ) {
                $target_parent = $current_language_term;
            } elseif ( $parent_source_lang && isset( $parent_translations[ $parent_source_lang ] ) ) {
                $target_parent = (int) $parent_translations[ $parent_source_lang ];
            } else {
                $target_parent = $parent_id;
            }
        }

        $sibling = get_term( $translated_term_id, $taxonomy );

        if ( ! $sibling || is_wp_error( $sibling ) ) {
            // Skip invalid translated terms.
            continue;
        }

        if ( (int) $sibling->parent === $target_parent ) {
            // No change needed when parent is already correct.
            continue;
        }

        // Prevent recursive syncing by setting a guard while updating child terms.
        // Prevent recursive syncing by setting a guard while updating child terms.
        $previous_state = ! empty( $GLOBALS['wpr_translation_syncing_term_parents'] );

        $GLOBALS['wpr_translation_syncing_term_parents'] = true;
        wp_update_term(
            $translated_term_id,
            $taxonomy,
            array(
                'parent' => $target_parent,
            )
        );

        if ( $previous_state ) {
            $GLOBALS['wpr_translation_syncing_term_parents'] = true;
        } else {
            unset( $GLOBALS['wpr_translation_syncing_term_parents'] );
        }
    }
}

/**
 * Filter term lists when taxonomies are configured to show only current language terms.
 *
 * @param array|WP_Term[] $terms       Retrieved terms.
 * @param array|string    $taxonomies  Taxonomies queried.
 * @param array           $args        Query arguments.
 * @param WP_Term_Query   $term_query  Term query instance.
 *
 * @return array Filtered terms.
 */
function wpr_translation_filter_terms_for_hidden_modes( $terms, $taxonomies, $args, $term_query ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
    if ( is_admin() || empty( $terms ) || is_wp_error( $terms ) ) {
        // Do not filter admin or empty/error results.
        return $terms;
    }

    if ( function_exists( 'wpr_translate_language_router_is_rest_request' ) && wpr_translate_language_router_is_rest_request() ) {
        // Keep REST payloads untouched to avoid breaking term assignment flows.
        return $terms;
    }


    $taxonomies = (array) $taxonomies;

    if ( empty( $taxonomies ) ) {
        // Nothing to filter without taxonomies.
        return $terms;
    }

    $needs_filter = false;

    foreach ( $taxonomies as $taxonomy ) {
        // Filter any taxonomy that participates in translation. Non-translatable taxonomies
        // remain untouched to avoid altering core term lists.
        if ( wpr_translation_is_taxonomy_translatable( $taxonomy ) ) {
            $needs_filter = true;
            break;
        }
    }

    if ( ! $needs_filter ) {
        // Skip when no translatable taxonomies were queried.
        return $terms;
    }

    $languages = wpr_translation_get_active_languages();

    if ( empty( $languages ) ) {
        // No active languages means no filtering.
        return $terms;
    }

    $current_language = '';

    if ( function_exists( 'wpr_translate_get_current_language' ) ) {
        // Use plugin language context first to avoid cookie/theme fallback drift.
        $language_context = wpr_translate_get_current_language();

        if ( is_array( $language_context ) && ! empty( $language_context['code'] ) ) {
            $current_language = sanitize_key( $language_context['code'] );
        } elseif ( is_string( $language_context ) ) {
            $current_language = sanitize_key( $language_context );
        }
    }

    if ( '' === $current_language && function_exists( 'wpestate_get_current_language' ) ) {
        // Keep legacy fallback only when plugin resolver returned nothing.
        $current_language = sanitize_key( wpestate_get_current_language() );
    }

    if ( '' !== $current_language && function_exists( 'wpr_translate_get_language' ) ) {
        $resolved = wpr_translate_get_language( $current_language );

        if ( is_array( $resolved ) && ! empty( $resolved['code'] ) ) {
            $current_language = sanitize_key( $resolved['code'] );
        }
    }

    if ( '' !== $current_language ) {
        // Fall back to the default language when the current language is unknown.
        $known_codes = array();
        $known_slugs = array();

        foreach ( $languages as $language ) {
            if ( isset( $language['code'] ) ) {
                $known_codes[] = sanitize_key( $language['code'] );
            }
            if ( isset( $language['slug'] ) ) {
                $known_slugs[] = sanitize_key( $language['slug'] );
            }
        }

        $known_codes = array_filter( $known_codes );
        $known_slugs = array_filter( $known_slugs );
        $known_ids   = array_unique( array_merge( $known_codes, $known_slugs ) );

        if ( ! in_array( $current_language, $known_ids, true ) ) {
            $current_language = '';
        }
    }

    if ( '' === $current_language ) {
        // Fall back to the default language.
        $current_language = wpr_translation_get_default_language_code( $languages );
    }

    if ( '' === $current_language ) {
        // Cannot filter without a language context.
        return $terms;
    }

    $default_language = wpr_translation_get_default_language_code( $languages );
    $taxonomy_hint    = '';

    if ( 1 === count( $taxonomies ) ) {
        // When a single taxonomy was queried, remember it for numeric term IDs.
        $taxonomy_hint = sanitize_key( $taxonomies[0] );
    }

    $filtered   = array();
    $seen_ids   = array();
    $seen_names = array();
    // Track rendered term names per taxonomy/parent to avoid duplicate labels in lists.
    $fields   = isset( $args['fields'] ) ? (string) $args['fields'] : 'all';
    $map_fields = array( 'id=>parent', 'id=>name', 'id=>slug' );
    $use_map     = in_array( $fields, $map_fields, true );
    $use_ids     = ( 'ids' === $fields );
    $use_tt_ids  = ( 'tt_ids' === $fields );
    $use_names   = ( 'names' === $fields );
    $use_objects = ! $use_map && ! $use_ids && ! $use_tt_ids && ! $use_names;

    foreach ( $terms as $term ) {
        $term_id  = 0;
        $taxonomy = '';

        if ( $term instanceof WP_Term ) {
            $term_id  = (int) $term->term_id;
            $taxonomy = sanitize_key( $term->taxonomy );
        } else {
            $term_id = (int) $term;

            if ( $term_id > 0 && '' !== $taxonomy_hint ) {
                $taxonomy = $taxonomy_hint;
            }
        }

        // Only filter terms that belong to translatable taxonomies. For non-translatable
        // taxonomies, return terms unchanged to preserve core behavior.
        if ( $term_id <= 0 || '' === $taxonomy || ! wpr_translation_is_taxonomy_translatable( $taxonomy ) ) {
            $filtered[] = $term;
            continue;
        }

        $canonical_term_id = $term_id;

        if ( function_exists( 'wpr_translate_resolve_original_term_id' ) ) {
            $resolved_canonical = absint( wpr_translate_resolve_original_term_id( $term_id, $taxonomy ) );

            if ( $resolved_canonical > 0 ) {
                $canonical_term_id = $resolved_canonical;
            }
        }

        $target_id = 0;

        if ( '' !== $default_language && $current_language === $default_language ) {
            // Default language should always return canonical/original term IDs.
            $target_id = $canonical_term_id;
        } elseif ( function_exists( 'wpr_translate_lookup_translated_term_id_strict' ) ) {
            // Non-default language should only return explicit mappings.
            $strict_target = wpr_translate_lookup_translated_term_id_strict( $canonical_term_id, $taxonomy, $current_language );

            if ( is_numeric( $strict_target ) ) {
                $target_id = absint( $strict_target );
            }
        }

        if ( $target_id <= 0 || isset( $seen_ids[ $target_id ] ) ) {
            continue;
        }

        $translated_term = get_term( $target_id, $taxonomy );

        if ( ! $translated_term || is_wp_error( $translated_term ) ) {
            continue;
        }

        $name_key   = strtolower( (string) $translated_term->name );
        $parent_key = (int) $translated_term->parent;
        $dedupe_key = $taxonomy . '|' . $parent_key . '|' . $name_key;
        if ( isset( $seen_names[ $dedupe_key ] ) ) {
            continue;
        }

        if ( $use_ids ) {
            $filtered[] = $target_id;
        } elseif ( $use_tt_ids ) {
            $filtered[] = (int) $translated_term->term_taxonomy_id;
        } elseif ( $use_names ) {
            $filtered[] = (string) $translated_term->name;
        } elseif ( $use_map ) {
            if ( 'id=>parent' === $fields ) {
                $filtered[ $target_id ] = (int) $translated_term->parent;
            } elseif ( 'id=>name' === $fields ) {
                $filtered[ $target_id ] = (string) $translated_term->name;
            } elseif ( 'id=>slug' === $fields ) {
                $filtered[ $target_id ] = (string) $translated_term->slug;
            }
        } else {
            $filtered[] = $translated_term;
        }

        $seen_ids[ $target_id ] = true;
        $seen_names[ $dedupe_key ] = true;
    }

    return $filtered;
}
add_filter( 'get_terms', 'wpr_translation_filter_terms_for_hidden_modes', 20, 4 );

/**
 * Ensure front-end term queries allow translation filters to run.
 *
 * @param array $args       Term query arguments.
 * @param array $taxonomies Taxonomies queried.
 *
 * @return array
 */
function wpr_translation_force_terms_filters_args( $args, $taxonomies ) {
    if ( is_admin() || wp_doing_ajax() ) {
        return $args;
    }

    if ( function_exists( 'wpr_translate_language_router_is_rest_request' ) && wpr_translate_language_router_is_rest_request() ) {
        return $args;
    }

    $taxonomies = (array) $taxonomies;

    if ( empty( $taxonomies ) ) {
        return $args;
    }

    foreach ( $taxonomies as $taxonomy ) {
        if ( wpr_translation_is_taxonomy_translatable( $taxonomy ) ) {
            $args['suppress_filters'] = false;
            break;
        }
    }

    return $args;
}
add_filter( 'get_terms_args', 'wpr_translation_force_terms_filters_args', 20, 2 );

