<?php
/**
 * Elementor translation payload builders.
 *
 * Supplies helper functions that read Elementor layouts and prepare
 * translation payloads based on the configured widget field list.
 *
 * @package WPR_Translate
 */
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}
/**
 * Determine if a page uses Elementor based on its meta flags.
 *
 * @param int    $post_id   Post ID.
 * @param string $post_type Post type slug.
 *
 * @return bool
 */
function wpr_translate_is_elementor_page( $post_id, $post_type ) {
    // Only pages store Elementor builder meta in this context.
    if ( 'page' !== $post_type ) {
        return false;
    }

    // Elementor sets both the data blob and edit mode flag when a page uses the builder.
    $has_elementor_data = get_post_meta( $post_id, '_elementor_data', true );
    $edit_mode          = get_post_meta( $post_id, '_elementor_edit_mode', true );

    return ! empty( $has_elementor_data ) || ! empty( $edit_mode );
}
/**
 * Load the Elementor widget translation map from the active theme or bundled defaults.
 *
 * @return array
 */
function wpr_translate_load_translatable_elementor_widget_fields() {
    // Initialize config containers and file paths.
    $config              = array();
    $child_config_data   = array();
    $parent_config_data  = array();
    $child_config_path   = trailingslashit( get_stylesheet_directory() ) . 'wpr/translatable_widget_fields.json';
    $parent_config_path  = trailingslashit( get_template_directory() ) . 'wpr/translatable_widget_fields.json';
    $default_config_path = WPR_TRANSLATE_PATH . 'assets/config/translatable_default_elementor_widgets.json';

    // Load active parent theme widget configuration when available.
    if ( file_exists( $parent_config_path ) && is_readable( $parent_config_path ) ) {
        $parent_config_data = json_decode( wp_kses_post( file_get_contents( $parent_config_path ) ), true );
    }

    // Load active child theme widget configuration when available.
    if ( file_exists( $child_config_path ) && is_readable( $child_config_path ) ) {
        $child_config_data = json_decode( wp_kses_post( file_get_contents( $child_config_path ) ), true );
    }

    // Load bundled defaults for core Elementor widgets.
    if ( file_exists( $default_config_path ) && is_readable( $default_config_path ) ) {
        $default_config = json_decode( wp_kses_post( file_get_contents( $default_config_path ) ), true );

        if ( is_array( $default_config ) ) {
            $config = $default_config;
        }
    }

    // Layer parent theme-defined widgets on top of defaults.
    if ( is_array( $parent_config_data ) ) {
        foreach ( $parent_config_data as $widget_key => $widget_fields ) {
            $config[ $widget_key ] = $widget_fields;
        }
    }

    // Layer child theme-defined widgets last so child overrides parent.
    if ( is_array( $child_config_data ) ) {
        foreach ( $child_config_data as $widget_key => $widget_fields ) {
            $config[ $widget_key ] = $widget_fields;
        }
    }

    return is_array( $config ) ? $config : array();
}

/**
 * Decode stored Elementor data into an array structure.
 *
 * @param mixed $raw_elementor_data Stored meta value.
 *
 * @return array
 */
function wpr_translate_decode_elementor_data( $raw_elementor_data ) {
    // Normalize serialized values before decoding JSON.
    $data = maybe_unserialize( $raw_elementor_data );

    // Elementor values are serialized JSON; decode them before traversal.
    if ( is_string( $data ) ) {
        $decoded = json_decode( $data, true );

        if ( is_array( $decoded ) ) {
            $data = $decoded;
        }
    }

    return is_array( $data ) ? $data : array();
}

/**
 * Normalize a list of IDs stored by Elementor widgets.
 *
 * @param mixed $value Stored Elementor setting value.
 *
 * @return array
 */
function wpr_translate_normalize_elementor_ids( $value ) {
    // Support JSON-encoded ID lists stored by widgets.
    if ( is_string( $value ) ) {
        $maybe_list = json_decode( $value, true );

        if ( is_array( $maybe_list ) ) {
            $value = $maybe_list;
        }
    }

    if ( ! is_array( $value ) ) {
        $value = array( $value );
    }

    return array_filter( array_map( 'absint', (array) $value ) );
}

/**
 * Normalize a list of taxonomy slugs stored by Elementor widgets.
 *
 * @param mixed $value Stored Elementor setting value.
 *
 * @return array
 */
function wpr_translate_normalize_elementor_slugs( $value ) {
    // Support JSON-encoded slug lists stored by widgets.
    if ( is_string( $value ) ) {
        $maybe_list = json_decode( $value, true );

        if ( is_array( $maybe_list ) ) {
            $value = $maybe_list;
        }
    }

    if ( ! is_array( $value ) ) {
        $value = array( $value );
    }

    $slugs = array();

    foreach ( $value as $raw_slug ) {
        $slug = sanitize_title( (string) $raw_slug );

        if ( '' === $slug || in_array( $slug, $slugs, true ) ) {
            continue;
        }

        $slugs[] = $slug;
    }

    return $slugs;
}

/**
 * Map Elementor field names to known taxonomy slugs when the field uses an alias.
 *
 * WP Residence widgets often store taxonomy references under field names like
 * `action_ids` even though the registered taxonomy is `property_action_category`.
 * Translating Elementor data requires the real taxonomy slug so lookups can find
 * translated term IDs. When no mapping is available the original field name is
 * returned.
 *
 * @param string $field_name Elementor widget field name.
 *
 * @param mixed $field_value                Optional field value used for numeric checks.
 * @param bool  $allow_alias_without_numeric True when alias mapping should run even for slug values.
 *
 * @return string
 */
function wpr_translate_resolve_elementor_taxonomy_slug( $field_name, $field_value = null, $allow_alias_without_numeric = false ) {
    // Normalize the field name before mapping.
    $field_name                  = sanitize_key( $field_name );
    $allow_alias_without_numeric = (bool) $allow_alias_without_numeric;

    // Only map fields that store numeric term IDs.
    $has_numeric_value = false;

    if ( is_array( $field_value ) ) {
        foreach ( $field_value as $value ) {
            if ( is_numeric( $value ) ) {
                $has_numeric_value = true;
                break;
            }
        }
    } elseif ( null !== $field_value && is_numeric( $field_value ) ) {
        $has_numeric_value = true;
    }

    if ( ! $has_numeric_value && ! $allow_alias_without_numeric ) {
        return $field_name;
    }

    // Map common WP Residence Elementor field aliases to their registered taxonomies.
    $alias_map = array(
        'action_ids'   => 'property_action_category',
        'action_data'  => 'property_action_category',
        'area_ids'     => 'property_area',
        'area_data'    => 'property_area',
        'category_ids' => 'property_category',
        'category_data' => 'property_category',
        'city_ids'     => 'property_city',
        'city_data'    => 'property_city',
        'county_data'  => 'property_county_state',
        'features_ids' => 'property_features',
        'state_ids'    => 'property_state',
        'status_ids'   => 'property_status',
        'status_data'  => 'property_status',
    );

    if ( isset( $alias_map[ $field_name ] ) && taxonomy_exists( $alias_map[ $field_name ] ) ) {
        return sanitize_key( $alias_map[ $field_name ] );
    }

    return $field_name;
}
/**
 * Collect taxonomy entries for an Elementor widget field following the payload schema used for taxonomies.
 *
 * @param mixed  $value      Elementor field value.
 * @param string $field_name Field name.
 * @param string $language   Target language slug.
 *
 * @return array
 */
function wpr_translate_collect_elementor_taxonomy_terms( $value, $field_name, $language ) {
    // Resolve taxonomy metadata for the field.
    $taxonomy        = wpr_translate_resolve_elementor_taxonomy_slug( $field_name, $value );
    $term_entries    = array();
    $taxonomy_object = taxonomy_exists( $taxonomy ) ? get_taxonomy( $taxonomy ) : null;
    $label           = $taxonomy_object instanceof WP_Taxonomy && isset( $taxonomy_object->label ) ? sanitize_text_field( $taxonomy_object->label ) : $taxonomy;
    $term_values = wpr_translate_normalize_elementor_ids( $value );

    foreach ( $term_values as $term_value ) {
        // Resolve translated term IDs when possible.
        $translated_term_id = '';

        if ( '' !== $language && function_exists( 'wpr_translate_lookup_translated_term_id_strict' ) ) {
            $translated_term_id = wpr_translate_lookup_translated_term_id_strict( $term_value, $taxonomy, $language );
        }

        // Fall back to the more permissive lookup when strict resolution fails so translated term IDs are still captured.
        if ( '' === $translated_term_id && '' !== $language && function_exists( 'wpr_translate_lookup_translated_term_id' ) ) {
            $permissive_term_id = wpr_translate_lookup_translated_term_id( $term_value, $taxonomy, $language );

            if ( $permissive_term_id && $permissive_term_id !== $term_value ) {
                $translated_term_id = $permissive_term_id;
            }
        }

        $preferred_term_id = $translated_term_id ? (int) $translated_term_id : (int) $term_value;
        $term_obj          = null;

        if ( $taxonomy && term_exists( $preferred_term_id, $taxonomy ) ) {
            $term_obj = get_term( $preferred_term_id, $taxonomy );
        }

        if ( ! ( $term_obj instanceof WP_Term ) && $preferred_term_id !== (int) $term_value && $taxonomy && term_exists( $term_value, $taxonomy ) ) {
            $term_obj = get_term( $term_value, $taxonomy );
        }

        if ( $term_obj instanceof WP_Term ) {
            $term_entries[] = array(
                'term_id' => (int) $term_obj->term_id,
                'slug'    => sanitize_title( $term_obj->slug ),
                'name'    => sanitize_text_field( $term_obj->name ),
            );
            continue;
        }

        // Fallback when the stored ID no longer resolves to a term object.
        $term_entries[] = array(
            'term_id' => (int) $preferred_term_id,
            'slug'    => sanitize_title( (string) $preferred_term_id ),
            'name'    => sanitize_text_field( (string) $preferred_term_id ),
        );
    }

    if ( empty( $term_entries ) ) {
        return array();
    }

    return array(
        'type'     => 'taxonomy',
        'taxonomy' => $taxonomy,
        'label'    => $label,
        'terms'    => $term_entries,
    );
}

/**
 * Collect taxonomy entries for Elementor fields that store term slugs.
 *
 * @param mixed  $value      Elementor field value.
 * @param string $field_name Field name.
 * @param string $language   Target language slug.
 *
 * @return array
 */
function wpr_translate_collect_elementor_taxonomy_slug_terms( $value, $field_name, $language ) {
    // Resolve taxonomy metadata for the field, including alias fields that store slugs.
    $taxonomy        = wpr_translate_resolve_elementor_taxonomy_slug( $field_name, $value, true );
    $term_entries    = array();
    $taxonomy_object = taxonomy_exists( $taxonomy ) ? get_taxonomy( $taxonomy ) : null;
    $label           = $taxonomy_object instanceof WP_Taxonomy && isset( $taxonomy_object->label ) ? sanitize_text_field( $taxonomy_object->label ) : $taxonomy;
    $term_values     = wpr_translate_normalize_elementor_slugs( $value );

    foreach ( $term_values as $term_slug ) {
        $source_term = ( $taxonomy && taxonomy_exists( $taxonomy ) ) ? get_term_by( 'slug', $term_slug, $taxonomy ) : null;

        if ( $source_term instanceof WP_Term ) {
            $source_term_id      = (int) $source_term->term_id;
            $translated_term_id  = '';
            $preferred_term_id   = $source_term_id;
            $preferred_term_term = $source_term;

            if ( '' !== $language && function_exists( 'wpr_translate_lookup_translated_term_id_strict' ) ) {
                $translated_term_id = wpr_translate_lookup_translated_term_id_strict( $source_term_id, $taxonomy, $language );
            }

            // Fall back to the permissive lookup so translated terms are still captured when strict lookup misses.
            if ( '' === $translated_term_id && '' !== $language && function_exists( 'wpr_translate_lookup_translated_term_id' ) ) {
                $permissive_term_id = wpr_translate_lookup_translated_term_id( $source_term_id, $taxonomy, $language );

                if ( $permissive_term_id && $permissive_term_id !== $source_term_id ) {
                    $translated_term_id = $permissive_term_id;
                }
            }

            if ( $translated_term_id ) {
                $preferred_term_id = (int) $translated_term_id;
            }

            if ( $preferred_term_id > 0 && $preferred_term_id !== $source_term_id && taxonomy_exists( $taxonomy ) ) {
                $maybe_term = get_term( $preferred_term_id, $taxonomy );

                if ( $maybe_term instanceof WP_Term ) {
                    $preferred_term_term = $maybe_term;
                }
            }

            if ( $preferred_term_term instanceof WP_Term ) {
                $term_entries[] = array(
                    'term_id' => (int) $preferred_term_term->term_id,
                    'slug'    => sanitize_title( $preferred_term_term->slug ),
                    'name'    => sanitize_text_field( $preferred_term_term->name ),
                );
                continue;
            }

            // Keep source values when the translated term cannot be resolved.
            $term_entries[] = array(
                'term_id' => $source_term_id,
                'slug'    => sanitize_title( $source_term->slug ),
                'name'    => sanitize_text_field( $source_term->name ),
            );
            continue;
        }

        // Preserve unresolved source slug so apply can keep the original selection.
        $term_entries[] = array(
            'term_id' => 0,
            'slug'    => sanitize_title( $term_slug ),
            'name'    => sanitize_text_field( $term_slug ),
        );
    }

    if ( empty( $term_entries ) ) {
        return array();
    }

    return array(
        'type'     => 'taxonomy_slug',
        'taxonomy' => $taxonomy,
        'label'    => $label,
        'terms'    => $term_entries,
    );
}
/**
 * Recursively inspect Elementor elements and collect translatable widget settings.
 *
 * @param array  $elements            Elementor element tree.
 * @param array  $config              Widget field configuration.
 * @param string $language            Target language slug.
 * @param array  $elementor_translate Accumulator for widget data.
 */
function wpr_translate_collect_elementor_widget_translatables( $elements, $config, $language, &$elementor_translate ) {
    foreach ( $elements as $element ) {
        // Walk nested elements before working with the current node.
        if ( isset( $element['elements'] ) && is_array( $element['elements'] ) ) {
            // Walk nested elements before working with the current node.
            wpr_translate_collect_elementor_widget_translatables( $element['elements'], $config, $language, $elementor_translate );
        }

        if ( ! isset( $element['widgetType'] ) ) {
            continue;
        }

        $widget_type = sanitize_text_field( (string) $element['widgetType'] );
        $element_id  = isset( $element['id'] ) ? sanitize_key( (string) $element['id'] ) : '';

        if ( '' === $widget_type || '' === $element_id || ! isset( $config[ $widget_type ] ) ) {
            // Allow Search Form Builder repeater fields even when config omits form_fields.
            if ( 'WpResidence_Search_Form_Builder' !== $widget_type ) {
                continue;
            }
        }

        $settings = isset( $element['settings'] ) && is_array( $element['settings'] ) ? $element['settings'] : array();

        if ( 'WpResidence_Search_Form_Builder' === $widget_type && isset( $settings['form_fields'] ) && is_array( $settings['form_fields'] ) ) {
            foreach ( $settings['form_fields'] as $index => $item ) {
                if ( ! is_array( $item ) ) {
                    continue;
                }

                if ( isset( $item['field_label'] ) ) {
                    $elementor_translate['translated_strings'][ $widget_type ][ $element_id ]['form_fields'][ $index ]['field_label'] = wp_kses_post( (string) $item['field_label'] );
                }

                if ( isset( $item['placeholder'] ) ) {
                    $elementor_translate['translated_strings'][ $widget_type ][ $element_id ]['form_fields'][ $index ]['placeholder'] = wp_kses_post( (string) $item['placeholder'] );
                }
            }
        }

        if ( ! isset( $config[ $widget_type ] ) ) {
            continue;
        }

        foreach ( $config[ $widget_type ] as $field_config ) {
            if ( ! is_array( $field_config ) ) {
                continue;
            }

            foreach ( $field_config as $field_name => $field_rules ) {
                // Skip fields missing from the current element settings.
                $field_rules = is_array( $field_rules ) ? $field_rules : array();
                $field_type  = isset( $field_rules['type'] ) ? sanitize_key( $field_rules['type'] ) : 'text';

                if ( ! isset( $settings[ $field_name ] ) ) {
                    continue;
                }

                $field_value = $settings[ $field_name ];

                // Handle repeater fields for the Search Form Builder widget.
                if ( 'WpResidence_Search_Form_Builder' === $widget_type && 'form_fields' === $field_name && is_array( $field_value ) ) {
                    foreach ( $field_value as $index => $item ) {
                        if ( ! is_array( $item ) ) {
                            continue;
                        }

                        if ( isset( $item['field_label'] ) ) {
                            $elementor_translate['translated_strings'][ $widget_type ][ $element_id ][ $field_name ][ $index ]['field_label'] = wp_kses_post( (string) $item['field_label'] );
                        }

                        if ( isset( $item['placeholder'] ) ) {
                            $elementor_translate['translated_strings'][ $widget_type ][ $element_id ][ $field_name ][ $index ]['placeholder'] = wp_kses_post( (string) $item['placeholder'] );
                        }
                    }

                    continue;
                }

                if ( in_array( $field_type, array( 'taxonomy', 'taxonomy_slug' ), true ) ) {
                    // Collect taxonomy payloads for term-ID and term-slug based widget fields.
                    $taxonomy_entry = 'taxonomy_slug' === $field_type
                        ? wpr_translate_collect_elementor_taxonomy_slug_terms( $field_value, $field_name, $language )
                        : wpr_translate_collect_elementor_taxonomy_terms( $field_value, $field_name, $language );

                    if ( ! empty( $taxonomy_entry ) ) {
                        $elementor_translate['taxonomies'][ $widget_type ][ $element_id ][ $field_name ] = $taxonomy_entry;
                    }

                    continue;
                }

                if ( in_array( $field_type, array( 'id', 'post_id' ), true ) ) {
                    // Normalize and translate post ID references.
                    $ids            = wpr_translate_normalize_elementor_ids( $field_value );
                    $translated_ids = array();

                    foreach ( $ids as $id_value ) {
                        // Look up translated post IDs when available so builder links stay in sync.
                        $translated_ids[] = function_exists( 'wpr_translate_resolve_translated_post_id' ) ? wpr_translate_resolve_translated_post_id( $id_value, $language ) : $id_value;
                    }

                    $elementor_translate['translated_post_id'][ $widget_type ][ $element_id ][ $field_name ] = array(
                        'type'              => 'id',
                        'original'          => $ids,
                        'translated_values' => array_filter( array_map( 'absint', $translated_ids ) ),
                    );

                    continue;
                }

                // Default branch handles plain string fields or complex values serialised to JSON.
                $elementor_translate['translated_strings'][ $widget_type ][ $element_id ][ $field_name ] = is_scalar( $field_value ) ? wp_kses_post( (string) $field_value ) : wp_json_encode( $field_value );
            }
        }
    }
}

/**
 * Build the Elementor translation payload for a post.
 *
 * @param int    $post_id  Post ID.
 * @param string $language Target language slug.
 *
 * @return array
 */
function wpr_translate_prepare_elementor_translation_payload( $post_id, $language ) {
    // Load and decode Elementor data for the post.
    $raw_elementor_data = get_post_meta( $post_id, '_elementor_data', true );
    $elementor_data     = wpr_translate_decode_elementor_data( $raw_elementor_data );

    if ( empty( $elementor_data ) ) {
        return array();
    }

    // Load the widget field configuration for translation.
    $config = wpr_translate_load_translatable_elementor_widget_fields();

    if ( empty( $config ) ) {
        return array();
    }

    // Initialize the translation payload structure.
    $elementor_translate = array(
        'translated_strings' => array(),
        'translated_post_id' => array(),
        'taxonomies'         => array(),
    );

    // Populate the translation payload with eligible widget fields.
    // Populate the translation payload with eligible widget fields.
    wpr_translate_collect_elementor_widget_translatables( $elementor_data, $config, $language, $elementor_translate );

    foreach ( array_keys( $elementor_translate ) as $section_key ) {
        if ( empty( $elementor_translate[ $section_key ] ) ) {
            unset( $elementor_translate[ $section_key ] );
        }
    }

    return $elementor_translate;
}

