<?php
/**
 * Compatibility helpers for translating WPResidence shortcodes.
 *
 * This module inspects shortcode attributes that reference post IDs and
 * replaces them with the equivalent translation for the active language so the
 * rendered output stays consistent across the site.
 *
 * @package WPR_Translate
 */

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

/**
 * Attach shortcode compatibility filters on first invocation.
 *
 * @return void
 */
function wpr_translate_bootstrap_shortcode_compat() {
    // Guard against duplicate registration when the bootstrap runs multiple times.
    static $bootstrapped = false;

    if ( $bootstrapped ) {
        // Avoid duplicate filter registration.
        return;
    }

    // Register the handler that rewrites shortcode attributes prior to execution.
    $bootstrapped = true;
    add_filter( 'pre_do_shortcode_tag', 'wpr_translate_filter_wpresidence_shortcodes', 10, 4 );
}

/**
 * Determine whether Elementor/shortcode compatibility is enabled.
 *
 * @return bool True when the integration should run, false otherwise.
 */
function wpr_translate_is_elementor_compat_enabled() {
    // Pull plugin settings so the toggle can be honoured.
    $settings = get_option( 'wpr_translate_settings', array() );

    if ( ! is_array( $settings ) || ! isset( $settings['elementor_compatibility'] ) ) {
        // Default to enabled when the setting is missing.
        return true;
    }

    return ! empty( $settings['elementor_compatibility'] );
}

/**
 * Rewrite WPResidence shortcode attributes so they target translated posts.
 *
 * @param mixed  $output Short-circuit output from earlier filters.
 * @param string $tag    Shortcode tag being executed.
 * @param array  $attr   Attributes supplied to the shortcode.
 * @param array  $m      Regex match data from do_shortcode().
 *
 * @return mixed Modified output or null to continue default processing.
 */
function wpr_translate_filter_wpresidence_shortcodes( $output, $tag, $attr, $m ) {
    // Respect previous filters and bail if compatibility is disabled.
    if ( null !== $output || ! wpr_translate_is_elementor_compat_enabled() ) {
        return $output;
    }

    $tag = strtolower( (string) $tag );

    // Only act on WPResidence shortcodes that we understand.
    if ( '' === $tag || ! wpr_translate_is_wpresidence_shortcode_tag( $tag ) ) {
        // Not a supported WPResidence shortcode.
        return null;
    }

    // Ensure attributes are always represented as an array for processing.
    if ( ! is_array( $attr ) ) {
        // Normalize non-array attributes to an empty array.
        $attr = array();
    }

    // Attempt to translate any ID-based attributes.
    $translated_attr = wpr_translate_rewrite_shortcode_attributes( $tag, $attr );

    // If nothing changed, allow WordPress to handle execution normally.
    if ( $translated_attr === $attr ) {
        // No changes detected, allow normal shortcode execution.
        return null;
    }

    global $shortcode_tags;

    // Guard against undefined or invalid shortcode callbacks.
    if ( ! isset( $shortcode_tags[ $tag ] ) || ! is_callable( $shortcode_tags[ $tag ] ) ) {
        // Skip when the shortcode callback is missing.
        return null;
    }

    $callback = $shortcode_tags[ $tag ];
    $content  = isset( $m[5] ) ? $m[5] : null;

    // Prevent infinite recursion when callbacks re-trigger do_shortcode().
    static $processing = array();

    if ( isset( $processing[ $tag ] ) ) {
        // Prevent recursion if the shortcode renders itself.
        return null;
    }

    // Mark the shortcode as in-flight so recursive rendering stops early.
    $processing[ $tag ] = true;

    // Execute the original callback with translated attributes.
    $result = call_user_func( $callback, $translated_attr, $content, $tag );

    // Remove the recursion flag now that rendering is finished.
    unset( $processing[ $tag ] );

    return $result;
}

/**
 * Check whether the provided shortcode tag belongs to WPResidence.
 *
 * @param string $tag Shortcode tag name.
 *
 * @return bool True when the tag should be processed, false otherwise.
 */
function wpr_translate_is_wpresidence_shortcode_tag( $tag ) {
    if ( '' === $tag ) {
        // Empty tag cannot be matched.
        return false;
    }

    // Support the naming conventions used across the theme.
    if ( 0 === strpos( $tag, 'wpestate_' ) || 0 === strpos( $tag, 'wpresidence_' ) || 0 === strpos( $tag, 'estate_' ) ) {
        // Known WPResidence shortcode prefixes.
        return true;
    }

    $extra = array( 'property_page', 'property_page_advanced', 'estate_property', 'estate_properties' );

    return in_array( $tag, $extra, true );
}

/**
 * Translate any ID-based shortcode attributes.
 *
 * @param string $tag        Shortcode tag.
 * @param array  $attributes Attributes supplied to the shortcode.
 *
 * @return array Translated attributes or original values when unchanged.
 */
function wpr_translate_rewrite_shortcode_attributes( $tag, $attributes ) {
    $updated = array();
    $changed = false;

    foreach ( $attributes as $key => $value ) {
        $updated_value = $value;

        if ( ! is_int( $key ) ) {
            // Identify whether this attribute should contain a single ID or list.
            $attribute_type = wpr_translate_identify_shortcode_attribute_type( $key );

            if ( 'single' === $attribute_type ) {
                // Convert single IDs to the language-appropriate counterpart.
                $maybe_translated = wpr_translate_convert_shortcode_single_id( $value );

                if ( null !== $maybe_translated ) {
                    $updated_value = $maybe_translated;
                }
            } elseif ( 'list' === $attribute_type ) {
                // Convert lists of IDs to the language-appropriate counterparts.
                $maybe_translated = wpr_translate_convert_shortcode_id_list( $value );

                if ( null !== $maybe_translated ) {
                    $updated_value = $maybe_translated;
                }
            }
        }

        if ( $updated_value !== $value ) {
            // Track when any attribute was updated.
            $changed = true;
        }

        // Persist the potentially updated attribute for the callback call.
        $updated[ $key ] = $updated_value;
    }

    return $changed ? $updated : $attributes;
}

/**
 * Determine whether an attribute likely stores a single ID or a list of IDs.
 *
 * @param string $attribute Attribute name.
 *
 * @return string Either 'single', 'list', or empty string for unknown.
 */
function wpr_translate_identify_shortcode_attribute_type( $attribute ) {
    $attribute = strtolower( (string) $attribute );

    if ( '' === $attribute ) {
        // No attribute name means no match.
        return '';
    }

    $single_names = array( 'id', 'post_id', 'postid', 'property_id', 'propertyid', 'agent_id', 'agentid', 'developer_id', 'developerid' );
    $list_names   = array( 'ids', 'post_ids', 'postids', 'property_ids', 'propertyids', 'agent_ids', 'agentids', 'developer_ids', 'developerids' );

    if ( in_array( $attribute, $single_names, true ) ) {
        // Attribute names that indicate a single ID.
        return 'single';
    }

    if ( in_array( $attribute, $list_names, true ) ) {
        // Attribute names that indicate a list of IDs.
        return 'list';
    }

    if ( preg_match( '/(?:^|_)(post|property|agent|developer|estate|listing|item)id$/', $attribute ) ) {
        // Fallback regex for single ID keys.
        return 'single';
    }

    if ( preg_match( '/(?:^|_)(post|property|agent|developer|estate|listing|item)ids$/', $attribute ) ) {
        // Fallback regex for list ID keys.
        return 'list';
    }

    return '';
}

/**
 * Convert a single ID attribute to the translated post ID when possible.
 *
 * @param mixed $value Attribute value supplied to the shortcode.
 *
 * @return string|null Translated ID string or null when unchanged.
 */
function wpr_translate_convert_shortcode_single_id( $value ) {
    if ( is_array( $value ) ) {
        // Arrays are handled by the list converter.
        return null;
    }

    $value = trim( (string) $value );

    if ( '' === $value || ! is_numeric( $value ) ) {
        // Only translate numeric IDs.
        return null;
    }

    $post_id       = (int) $value;
    $translated_id = wpr_translate_resolve_post_for_current_language( $post_id );

    return ( $translated_id && $translated_id !== $post_id ) ? (string) $translated_id : null;
}

/**
 * Convert a list of IDs to their language-specific counterparts.
 *
 * @param mixed $value Attribute value supplied to the shortcode.
 *
 * @return string|null Updated list string or null when unchanged.
 */
function wpr_translate_convert_shortcode_id_list( $value ) {
    if ( is_array( $value ) ) {
        $converted = array();
        $changed   = false;

        foreach ( $value as $item ) {
            $item = trim( (string) $item );

            if ( '' === $item || ! is_numeric( $item ) ) {
                // Preserve non-numeric entries exactly as authored.
                $converted[] = $item;
                continue;
            }

            $post_id       = (int) $item;
            $translated_id = wpr_translate_resolve_post_for_current_language( $post_id );

            if ( $translated_id && $translated_id !== $post_id ) {
                // Replace with translated ID when available.
                $converted[] = (string) $translated_id;
                $changed     = true;
            } else {
                $converted[] = $item;
            }
        }

        return $changed ? implode( ',', $converted ) : null;
    }

    $value = trim( (string) $value );

    if ( '' === $value ) {
        // Empty list cannot be converted.
        return null;
    }

    $delimiter = ',';

    if ( false !== strpos( $value, '|' ) ) {
        // Prefer pipe as the list delimiter when present.
        $delimiter = '|';
    } elseif ( false !== strpos( $value, ';' ) ) {
        $delimiter = ';';
    } elseif ( false !== strpos( $value, ' ' ) ) {
        $delimiter = ' ';
    }

    $parts   = array_map( 'trim', explode( $delimiter, $value ) );
    $changed = false;

    foreach ( $parts as $index => $part ) {
        if ( '' === $part || ! is_numeric( $part ) ) {
            continue;
        }

        $post_id       = (int) $part;
        $translated_id = wpr_translate_resolve_post_for_current_language( $post_id );

        if ( $translated_id && $translated_id !== $post_id ) {
            // Replace the entry with the translated ID.
            $parts[ $index ] = (string) $translated_id;
            $changed         = true;
        }
    }

    if ( ! $changed ) {
        // Return null when no IDs were updated.
        return null;
    }

    // Reassemble the list using the original delimiter (spaces use a single space).
    $glue = ( ' ' === $delimiter ) ? ' ' : $delimiter;

    return implode( $glue, $parts );
}

/**
 * Resolve the current-language equivalent for a post ID.
 *
 * @param int $post_id Original post ID referenced by a shortcode.
 *
 * @return int Translated post ID or 0 when unavailable.
 */
function wpr_translate_resolve_post_for_current_language( $post_id ) {
    $post_id = absint( $post_id );

    if ( ! $post_id ) {
        // Invalid post ID.
        return 0;
    }

    $post = get_post( $post_id );

    if ( ! $post instanceof WP_Post ) {
        // No post means no translation.
        return 0;
    }

    $language_code = '';

    if ( function_exists( 'wpr_translate_get_current_language' ) ) {
        // The helper may return an array with detailed language data.
        $language = wpr_translate_get_current_language();

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

    return wpr_translate_resolve_post_for_language( $post_id, $post->post_type, $language_code );
}

/**
 * Resolve the best translation for a post within a specific language context.
 *
 * @param int    $post_id   Source post ID.
 * @param string $post_type Post type slug for the source post.
 * @param string $language  Language code that should be preferred.
 *
 * @return int Post ID that should be used for rendering.
 */
function wpr_translate_resolve_post_for_language( $post_id, $post_type, $language ) {
    // Ensure translation helpers are available before continuing.
    if ( ! function_exists( 'wpr_translation_get_element_type' ) || ! function_exists( 'wpr_translation_get_translation_group' ) ) {
        // Without helpers, return the original post ID.
        return $post_id;
    }

    $element_type = wpr_translation_get_element_type( $post_type );
    $group        = wpr_translation_get_translation_group( $post_id, $element_type );

    if ( empty( $group['translations'] ) || ! is_array( $group['translations'] ) ) {
        // No translations found for this post.
        return $post_id;
    }

    $language = sanitize_key( $language );

    // Prefer an exact match for the requested language.
    if ( '' !== $language && isset( $group['translations'][ $language ] ) ) {
        $candidate = (int) $group['translations'][ $language ];

        if ( wpr_translation_is_translation_post_available( $candidate ) ) {
            // Use the language-specific translation.
            return $candidate;
        }
    }

    // Fall back to the source-language translation when available.
    if ( ! empty( $group['source_language'] ) && isset( $group['translations'][ $group['source_language'] ] ) ) {
        $candidate = (int) $group['translations'][ $group['source_language'] ];

        if ( wpr_translation_is_translation_post_available( $candidate ) ) {
            // Fall back to the source-language translation.
            return $candidate;
        }
    }

    // Ultimately fall back to the first available translation within the group.
    foreach ( $group['translations'] as $candidate ) {
        $candidate = (int) $candidate;

        if ( wpr_translation_is_translation_post_available( $candidate ) ) {
            // Use the first available translation.
            return $candidate;
        }
    }

    return $post_id;
}
