<?php
/**
 * Bot Resolver - Determines which bot to display on current page.
 *
 * @package BoostBot
 * @since   1.1.0
 */

// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Class BoostBot_Bot_Resolver
 *
 * Resolves which bot should be displayed based on display conditions
 * and current page context.
 *
 * @since 1.1.0
 */
class BoostBot_Bot_Resolver {

    /**
     * Get the appropriate bot for the current context.
     *
     * Prioritizes bots with more specific display conditions over broader ones.
     * For example, a bot set to show on specific pages will take precedence
     * over a bot set to show on "all site".
     *
     * @param array $context Optional. Page context override.
     * @return object|null The bot object or null.
     */
    public function resolve( $context = array() ) {
        // Build context if not provided
        if ( empty( $context ) ) {
            $context = $this->build_current_context();
        }

        $language = $context['language'] ?? '';
        $model    = new BoostBot_Bot_Model();

        // Fetch bots filtered by language when available
        $active_bots = $language ? $model->get_active( $language ) : $model->get_active();

        // Filter out test_mode bots for non-admin users.
        $is_admin_user = is_user_logged_in() && current_user_can( 'manage_options' );
        if ( ! $is_admin_user ) {
            $active_bots = array_filter( $active_bots, function( $b ) {
                return empty( $b->test_mode ) || ! (int) $b->test_mode;
            } );
            $active_bots = array_values( $active_bots );
        }

        if ( empty( $active_bots ) ) {
            $default_bot = $model->get_default( $language );
            if ( $default_bot && ! $is_admin_user && ! empty( $default_bot->test_mode ) && (int) $default_bot->test_mode ) {
                return null;
            }
            return $default_bot;
        }

        // Collect all matching bots with their specificity scores
        $matching_bots = array();
        foreach ( $active_bots as $bot ) {
            if ( $this->bot_matches_context( $bot, $context ) ) {
                $specificity            = $this->calculate_specificity( $bot, $context );
                $matching_bots[]        = array(
                    'bot'         => $bot,
                    'specificity' => $specificity,
                    'priority'    => (int) ( $bot->priority ?? 0 ),
                );
            }
        }

        // Also check default bot (skip if test_mode and user is not admin)
        $default_bot = $model->get_default( $language );
        if ( $default_bot && ! $is_admin_user && ! empty( $default_bot->test_mode ) && (int) $default_bot->test_mode ) {
            $default_bot = null; // Hide test-mode default bot from non-admins
        }
        if ( $default_bot && $this->bot_matches_context( $default_bot, $context ) ) {
            // Check if default bot is already in the matching list (it might be active too)
            $default_in_list = false;
            foreach ( $matching_bots as $mb ) {
                if ( (int) $mb['bot']->id === (int) $default_bot->id ) {
                    $default_in_list = true;
                    break;
                }
            }
            if ( ! $default_in_list ) {
                $matching_bots[] = array(
                    'bot'         => $default_bot,
                    'specificity' => $this->calculate_specificity( $default_bot, $context ),
                    'priority'    => (int) ( $default_bot->priority ?? 0 ),
                );
            }
        }

        // If we have matching bots, return the most specific one
        if ( ! empty( $matching_bots ) ) {
            // Sort by specificity (highest first), then by priority as tiebreaker
            usort( $matching_bots, function( $a, $b ) {
                if ( $a['specificity'] !== $b['specificity'] ) {
                    return $b['specificity'] - $a['specificity'];
                }
                return $b['priority'] - $a['priority'];
            } );
            return $matching_bots[0]['bot'];
        }

        // Safety net: if no default bot is configured (or it isn't active),
        // fall back to the highest-priority active bot even if its include conditions don't match.
        // This prevents the entire widget from disappearing due to misconfiguration.
        if ( empty( $default_bot ) && ! empty( $active_bots ) ) {
            usort( $active_bots, function( $a, $b ) {
                return (int) $b->priority - (int) $a->priority;
            } );
            return $active_bots[0];
        }

        return null;
    }

    /**
     * Calculate specificity score for a bot's conditions.
     *
     * Higher score = more specific conditions.
     * Specific page/post IDs are more specific than post types,
     * which are more specific than "all site".
     *
     * @param object $bot     The bot object.
     * @param array  $context The context array.
     * @return int Specificity score.
     */
    public function calculate_specificity( $bot, $context ) {
        // Default bot with no conditions = lowest specificity
        if ( ! empty( $bot->is_default ) ) {
            $conditions = $bot->display_conditions;
            if ( is_string( $conditions ) && ! empty( $conditions ) ) {
                $conditions = json_decode( $conditions, true );
            }
            // Default bot only has excludes, no include conditions = show everywhere
            if ( empty( $conditions ) || ! is_array( $conditions ) ) {
                return 5; // Lowest specificity
            }
            // Check if it only has exclude conditions (meaning show everywhere except...)
            $has_includes = false;
            foreach ( $conditions as $cond ) {
                if ( isset( $cond['operator'] ) && 'include' === $cond['operator'] && ( $cond['type'] ?? '' ) !== 'all' ) {
                    $has_includes = true;
                    break;
                }
            }
            if ( ! $has_includes ) {
                return 5; // Show everywhere = lowest specificity
            }
        }

        $conditions = $bot->display_conditions;
        if ( is_string( $conditions ) && ! empty( $conditions ) ) {
            $conditions = json_decode( $conditions, true );
        }

        if ( empty( $conditions ) || ! is_array( $conditions ) ) {
            return 10; // No conditions = low specificity
        }

        // Find the highest specificity among matching include conditions
        $max_specificity = 0;

        foreach ( $conditions as $condition ) {
            if ( ! isset( $condition['type'] ) || ! isset( $condition['operator'] ) ) {
                continue;
            }
            // Only consider include conditions for specificity
            if ( 'include' !== $condition['operator'] ) {
                continue;
            }
            // Only count if this condition actually matches
            if ( ! $this->condition_matches( $condition, $context ) ) {
                continue;
            }

            $specificity = $this->get_condition_specificity( $condition );
            if ( $specificity > $max_specificity ) {
                $max_specificity = $specificity;
            }
        }

        return $max_specificity > 0 ? $max_specificity : 10;
    }

    /**
     * Get specificity score for a single condition type.
     *
     * @param array $condition The condition array.
     * @return int Specificity score.
     */
    private function get_condition_specificity( $condition ) {
        $type  = $condition['type'] ?? '';
        $value = $condition['value'] ?? null;

        switch ( $type ) {
            case 'all':
                // "All site" = lowest specificity for include conditions
                return 10;

            case 'homepage':
                // Homepage is fairly specific (one page)
                return 80;

            case 'page':
                // Specific page IDs = very high specificity
                return 100;

            case 'post_type':
                // Check if it's specific items or all of type
                if ( is_string( $value ) && strpos( $value, ':' ) !== false ) {
                    // Specific item(s): post_type:id or post_type:id1,id2,id3
                    $parts = explode( ':', $value, 2 );
                    $ids   = isset( $parts[1] ) ? explode( ',', $parts[1] ) : array();
                    // More specific if fewer items selected
                    $count = count( array_filter( $ids ) );
                    if ( $count > 0 && $count <= 5 ) {
                        return 100; // 1-5 specific items = highest specificity
                    } elseif ( $count > 5 && $count <= 20 ) {
                        return 90; // 6-20 items = very high
                    } elseif ( $count > 20 ) {
                        return 70; // Many items = moderate-high
                    }
                    return 100; // Default for specific items
                }
                // All items of post type (e.g., all pages, all products)
                return 50;

            case 'taxonomy':
                // Category/tag filtering = moderately specific
                return 60;

            case 'url_contains':
                // URL pattern matching = moderate specificity
                return 55;

            case 'url_exact':
                // Exact URL = very specific
                return 95;

            case 'user_role':
                // User role = moderate specificity
                return 40;

            default:
                return 20;
        }
    }

    /**
     * Build context from current WordPress state.
     *
     * @return array The context array.
     */
    public function build_current_context() {
        global $post, $wp;

        $lang_manager = BoostBot_Language_Manager::get_instance();
        $post_id_val  = get_the_ID();

        $context = array(
            'is_home'            => is_front_page() || is_home(),
            'is_singular'        => is_singular(),
            'is_archive'         => is_archive(),
            'is_search'          => is_search(),
            'is_404'             => is_404(),
            'post_type'          => get_post_type(),
            'post_id'            => $post_id_val,
            'page_template'      => get_page_template_slug(),
            'url'                => home_url( $wp->request ),
            'user_role'          => is_user_logged_in() ? current( wp_get_current_user()->roles ) : 'guest',
            'language'           => $lang_manager->get_frontend_language( $post_id_val ?: 0 ),
            'categories'         => array(),
            'tags'               => array(),
            'product_categories' => array(),
        );

        // Get taxonomies
        if ( $post ) {
            $categories = get_the_category( $post->ID );
            if ( $categories && ! is_wp_error( $categories ) ) {
                $context['categories'] = wp_list_pluck( $categories, 'slug' );
            }

            $tags = get_the_tags( $post->ID );
            if ( $tags && ! is_wp_error( $tags ) ) {
                $context['tags'] = wp_list_pluck( $tags, 'slug' );
            }

            // WooCommerce categories
            if ( 'product' === $context['post_type'] ) {
                $product_cats = get_the_terms( $post->ID, 'product_cat' );
                if ( $product_cats && ! is_wp_error( $product_cats ) ) {
                    $context['product_categories'] = wp_list_pluck( $product_cats, 'slug' );
                }
            }
        }

        return $context;
    }

    /**
     * Build context from request parameters.
     *
     * @param int    $post_id  The post ID.
     * @param string $page_url The page URL.
     * @return array The context array.
     */
    public function build_context_from_request( $post_id = 0, $page_url = '' ) {
        $lang_manager = BoostBot_Language_Manager::get_instance();

        $context = array(
            'is_home'            => false,
            'is_singular'        => false,
            'is_archive'         => false,
            'is_search'          => false,
            'is_404'             => false,
            'post_type'          => '',
            'post_id'            => 0,
            'page_template'      => '',
            'url'                => $page_url,
            'user_role'          => is_user_logged_in() ? current( wp_get_current_user()->roles ) : 'guest',
            'language'           => $lang_manager->get_frontend_language( $post_id ?: 0 ),
            'categories'         => array(),
            'tags'               => array(),
            'product_categories' => array(),
        );

        if ( $post_id ) {
            $post_id                   = absint( $post_id );
            $context['post_id']        = $post_id;
            $context['post_type']      = get_post_type( $post_id );
            $context['page_template']  = get_page_template_slug( $post_id );
            $context['is_singular']    = true;
            $context['language']       = $lang_manager->get_frontend_language( $post_id );

            // Check if homepage
            $front_page_id = (int) get_option( 'page_on_front' );
            $context['is_home'] = ( $post_id === $front_page_id );

            // Get taxonomies
            $categories = get_the_category( $post_id );
            if ( $categories && ! is_wp_error( $categories ) ) {
                $context['categories'] = wp_list_pluck( $categories, 'slug' );
            }

            $tags = get_the_tags( $post_id );
            if ( $tags && ! is_wp_error( $tags ) ) {
                $context['tags'] = wp_list_pluck( $tags, 'slug' );
            }

            // WooCommerce categories
            if ( 'product' === $context['post_type'] ) {
                $product_cats = get_the_terms( $post_id, 'product_cat' );
                if ( $product_cats && ! is_wp_error( $product_cats ) ) {
                    $context['product_categories'] = wp_list_pluck( $product_cats, 'slug' );
                }
            }
        }

        return $context;
    }

    /**
     * Check if a bot matches the given context.
     *
     * @param object $bot     The bot object.
     * @param array  $context The context array.
     * @return bool True if matches.
     */
    public function bot_matches_context( $bot, $context ) {
        // Parse conditions
        $conditions = $bot->display_conditions;
        if ( is_string( $conditions ) && ! empty( $conditions ) ) {
            $conditions = json_decode( $conditions, true );
        }

        // Default bot: show everywhere by default, unless excluded by conditions.
        if ( ! empty( $bot->is_default ) ) {
            if ( empty( $conditions ) || ! is_array( $conditions ) ) {
                return true;
            }

            foreach ( $conditions as $condition ) {
                if ( ! isset( $condition['type'] ) || ! isset( $condition['operator'] ) ) {
                    continue;
                }
                if ( 'exclude' !== $condition['operator'] ) {
                    continue;
                }
                if ( $this->condition_matches( $condition, $context ) ) {
                    return false;
                }
            }

            return true;
        }

        // Non-default: no conditions => don't show
        if ( empty( $conditions ) || ! is_array( $conditions ) ) {
            return false;
        }

        // Process conditions
        $include_matches = false;
        $exclude_matches = false;
        $has_include     = false;

        foreach ( $conditions as $condition ) {
            if ( ! isset( $condition['type'] ) || ! isset( $condition['operator'] ) ) {
                continue;
            }

            $matches = $this->condition_matches( $condition, $context );

            if ( 'include' === $condition['operator'] ) {
                $has_include = true;
                if ( $matches ) {
                    $include_matches = true;
                }
            }

            if ( 'exclude' === $condition['operator'] && $matches ) {
                $exclude_matches = true;
            }
        }

        // Exclude takes precedence
        if ( $exclude_matches ) {
            return false;
        }

        // If no include conditions, treat as "show everywhere"
        if ( ! $has_include ) {
            return true;
        }

        return $include_matches;
    }

    /**
     * Check if a single condition matches.
     *
     * @param array $condition The condition array.
     * @param array $context   The context array.
     * @return bool True if condition matches.
     */
    private function condition_matches( $condition, $context ) {
        $type  = $condition['type'] ?? '';
        $value = $condition['value'] ?? null;

        switch ( $type ) {
            case 'all':
                return true;

            case 'homepage':
                return ! empty( $context['is_home'] );

            case 'page':
                $ids = is_array( $value ) ? array_map( 'absint', $value ) : array( absint( $value ) );
                return in_array( (int) $context['post_id'], $ids, true );

            case 'page_template':
                return $context['page_template'] === $value;

            case 'post_type':
                // Value can be "post_type" or "post_type:specific_id" or "post_type:id1,id2,id3"
                if ( is_string( $value ) && strpos( $value, ':' ) !== false ) {
                    // Specific item(s): post_type:id or post_type:id1,id2,id3
                    list( $post_type, $item_ids_str ) = explode( ':', $value, 2 );
                    
                    // First check if post type matches
                    if ( $context['post_type'] !== $post_type ) {
                        return false;
                    }
                    
                    // Parse comma-separated IDs
                    $item_ids = array_map( 'absint', array_filter( explode( ',', $item_ids_str ) ) );
                    
                    // If no specific IDs, match all of this post type
                    if ( empty( $item_ids ) ) {
                        return true;
                    }
                    
                    // Check if current post ID is in the list
                    return in_array( (int) $context['post_id'], $item_ids, true );
                }
                // All items of post type
                $types = is_array( $value ) ? $value : array( $value );
                return in_array( $context['post_type'], $types, true );

            case 'taxonomy':
                // Format: "taxonomy:term" e.g., "category:news"
                if ( is_string( $value ) && strpos( $value, ':' ) !== false ) {
                    list( $taxonomy, $term ) = explode( ':', $value, 2 );

                    if ( 'category' === $taxonomy ) {
                        return in_array( $term, $context['categories'] ?? array(), true );
                    }
                    if ( 'tag' === $taxonomy ) {
                        return in_array( $term, $context['tags'] ?? array(), true );
                    }
                    if ( 'product_cat' === $taxonomy ) {
                        return in_array( $term, $context['product_categories'] ?? array(), true );
                    }
                }
                return false;

            case 'url_contains':
                return is_string( $value ) && strpos( $context['url'] ?? '', $value ) !== false;

            case 'url_exact':
                return ( $context['url'] ?? '' ) === $value;

            case 'user_role':
                $roles = is_array( $value ) ? $value : array( $value );
                return in_array( $context['user_role'] ?? 'guest', $roles, true );

            default:
                return false;
        }
    }

    /**
     * Get bot configuration for frontend.
     *
     * @param object $bot The bot object.
     * @return array Configuration array.
     */
    public function get_bot_config( $bot ) {
        $config = array(
            'id'               => (int) $bot->id,
            'name'             => $bot->bot_name,
            'slug'             => $bot->bot_slug,
            'welcome_message'       => $bot->welcome_message,
            'show_welcome_message'  => isset( $bot->show_welcome_message ) ? (int) $bot->show_welcome_message : 1,
            'primary_color'    => $bot->primary_color ?: '#3BC2D0',
            'position'         => $bot->position ?: 'bottom-right',
            'bubble_icon'      => $bot->bubble_icon,
            'custom_instructions' => $bot->custom_instructions,
            'rules'            => $bot->rules,
            'forbidden_topics' => $bot->forbidden_topics,
            // Week 28: per-bot output cap (nullable; global enforced elsewhere)
            'bot_output_char_limit' => isset( $bot->bot_output_char_limit ) ? ( is_null( $bot->bot_output_char_limit ) ? null : (int) $bot->bot_output_char_limit ) : null,
        );
        
        // Load appearance preset if assigned, or use default
        $preset_model = new BoostBot_Appearance_Preset_Model();
        $preset = null;
        
        if ( ! empty( $bot->appearance_preset_id ) ) {
            $preset = $preset_model->get( $bot->appearance_preset_id );
        }
        
        // Fall back to default preset for the bot's language
        if ( ! $preset ) {
            $preset = $preset_model->get_default_for_language( $bot->language ?? '' );
        }
            
        if ( $preset ) {
                // Decode JSON fields - convert to array regardless of type
                $global_values = $preset->global_values;
                if ( is_string( $global_values ) ) {
                    $global_values = json_decode( $global_values, true );
                } elseif ( is_object( $global_values ) ) {
                    $global_values = json_decode( json_encode( $global_values ), true );
                }
                if ( ! is_array( $global_values ) ) {
                    $global_values = array();
                }
                
                // FALLBACK: If global_values is empty, build from flat preset fields
                if ( empty( $global_values ) || empty( $global_values['colors'] ) ) {
                    $global_values = array(
                        'colors' => array(
                            'primary'    => $preset->primary_color ?: '#3BC2D0',
                            'secondary'  => $preset->secondary_color ?: '#E32774',
                            'accent'     => $preset->secondary_color ?: '#E32774',
                            'background' => $preset->background_color ?: '#FFFFFF',
                            'surface'    => $preset->bot_message_bg ?: '#F9FAFB',
                            'text'       => $preset->bot_message_text ?: '#1F2937',
                            'textMuted'  => '#6B7280',
                            'border'     => $preset->input_border ?: '#E5E7EB',
                        ),
                        'typography' => array(
                            'fontFamily' => $preset->font_family ?: 'inherit',
                            'heading' => array(
                                'fontFamily' => $preset->font_family ?: 'inherit',
                                'fontSize'   => $preset->font_size_header ?: '18px',
                                'fontWeight' => $preset->font_weight_bold ?: '600',
                                'lineHeight' => $preset->line_height ?: '1.4',
                            ),
                            'body' => array(
                                'fontFamily'    => $preset->font_family ?: 'inherit',
                                'fontSize'      => $preset->font_size_base ?: '14px',
                                'fontWeight'    => $preset->font_weight_normal ?: '400',
                                'lineHeight'    => $preset->line_height ?: '1.5',
                                'letterSpacing' => $preset->letter_spacing ?: '0',
                            ),
                        ),
                        'effects' => array(
                            'borderRadiusSmall'  => '8px',
                            'borderRadiusMedium' => '12px',
                            'borderRadiusLarge'  => $preset->window_border_radius ?: '16px',
                            'shadowSmall'        => '0 2px 8px rgba(0,0,0,0.08)',
                            'shadowMedium'       => '0 4px 16px rgba(0,0,0,0.12)',
                            'shadowLarge'        => $preset->window_shadow ?: '0 10px 40px rgba(0,0,0,0.15)',
                        ),
                        'spacing' => array(
                            'xs' => '4px',
                            'sm' => '8px',
                            'md' => '16px',
                            'lg' => '24px',
                            'xl' => '32px',
                        ),
                    );
                }
                
                // Resolve _links: if preset colors are linked to system global values,
                // replace the stored color values with the linked system values.
                // This ensures the frontend receives the correct, resolved colors.
                if ( ! empty( $global_values['_links']['colors'] ) && is_array( $global_values['_links']['colors'] ) ) {
                    $bot_lang = $bot->language ?? 'he';
                    $system_global_values = get_option( 'boostbot_appearance_global_values_' . $bot_lang, array() );
                    if ( empty( $system_global_values ) ) {
                        $system_global_values = get_option( 'boostbot_appearance_global_values', array() );
                    }
                    if ( ! empty( $system_global_values ) && is_array( $system_global_values ) ) {
                        foreach ( $global_values['_links']['colors'] as $color_key => $ref ) {
                            if ( ! is_string( $ref ) || empty( $ref ) ) {
                                continue;
                            }
                            // Resolve "colors.primary" -> system_global_values['colors']['primary']
                            $parts   = explode( '.', $ref );
                            $current = $system_global_values;
                            foreach ( $parts as $part ) {
                                if ( ! is_array( $current ) || ! isset( $current[ $part ] ) ) {
                                    $current = null;
                                    break;
                                }
                                $current = $current[ $part ];
                            }
                            if ( is_string( $current ) && isset( $global_values['colors'] ) ) {
                                $global_values['colors'][ $color_key ] = $current;
                            }
                        }
                    }
                }

                $element_settings = $preset->element_settings;
                if ( is_string( $element_settings ) ) {
                    $element_settings = json_decode( $element_settings, true );
                } elseif ( is_object( $element_settings ) ) {
                    $element_settings = json_decode( json_encode( $element_settings ), true );
                }
                if ( ! is_array( $element_settings ) ) {
                    $element_settings = array();
                }
                
                // FALLBACK: If element_settings is empty, build from flat preset fields
                if ( empty( $element_settings ) ) {
                    $element_settings = array(
                        'chatBubble' => array(
                            'size'       => array( 'type' => 'custom', 'value' => $preset->bubble_size ?: '60px' ),
                            'background' => array( 'type' => 'global', 'ref' => 'colors.primary' ),
                            'icon'       => $preset->bubble_icon ?: 'robot',
                            'customIconUrl' => $preset->bubble_custom_icon ?: '',
                            'animation'  => ($preset->bubble_animation !== null && $preset->bubble_animation !== '') ? $preset->bubble_animation : 'none',
                            'position'   => 'bottom-right',
                            'margin'     => array( 'type' => 'custom', 'value' => '24px' ),
                        ),
                        'header' => array(
                            'background' => array( 'type' => 'custom', 'value' => $preset->header_background ?: $preset->primary_color ?: '#3BC2D0' ),
                            'text'       => array( 'type' => 'custom', 'value' => $preset->header_text_color ?: '#FFFFFF' ),
                            'height'     => array( 'type' => 'custom', 'value' => '60px' ),
                            'padding'    => array( 'type' => 'custom', 'value' => '0 16px' ),
                            'showCloseButton' => true,
                            'title'      => $bot->bot_name ?: 'BoostBot',
                            'subtitle'   => ( isset( $bot->language ) && 'he' !== $bot->language ) ? 'Online' : 'מקוון',
                        ),
                        'chatWindow' => array(
                            'width'        => array( 'type' => 'custom', 'value' => $preset->window_width ?: '380px' ),
                            'height'       => array( 'type' => 'custom', 'value' => $preset->window_height ?: '600px' ),
                            'background'   => array( 'type' => 'custom', 'value' => $preset->background_color ?: '#FFFFFF' ),
                            'borderRadius' => array( 'type' => 'custom', 'value' => $preset->window_border_radius ?: '16px' ),
                            'shadow'       => array( 'type' => 'custom', 'value' => $preset->window_shadow ?: '0 10px 40px rgba(0,0,0,0.15)' ),
                            'padding'      => array( 'type' => 'custom', 'value' => '0' ),
                        ),
                        'userMessage' => array(
                            'background'   => array( 'type' => 'custom', 'value' => $preset->user_message_bg ?: $preset->primary_color ?: '#3BC2D0' ),
                            'text'         => array( 'type' => 'custom', 'value' => $preset->user_message_text ?: '#FFFFFF' ),
                            'borderRadius' => array( 'type' => 'global', 'ref' => 'effects.borderRadiusMedium' ),
                            'padding'      => array( 'type' => 'custom', 'value' => '12px 16px' ),
                            'maxWidth'     => array( 'type' => 'custom', 'value' => '80%' ),
                        ),
                        'botMessage' => array(
                            'background'   => array( 'type' => 'custom', 'value' => $preset->bot_message_bg ?: '#F9FAFB' ),
                            'text'         => array( 'type' => 'custom', 'value' => $preset->bot_message_text ?: '#1F2937' ),
                            'borderRadius' => array( 'type' => 'global', 'ref' => 'effects.borderRadiusMedium' ),
                            'padding'      => array( 'type' => 'custom', 'value' => '12px 16px' ),
                            'maxWidth'     => array( 'type' => 'custom', 'value' => '80%' ),
                            'showAvatar'   => true,
                        ),
                        'inputArea' => array(
                            'background'   => array( 'type' => 'custom', 'value' => $preset->input_bg ?: '#FFFFFF' ),
                            'text'         => array( 'type' => 'custom', 'value' => $preset->input_text ?: '#1F2937' ),
                            'border'       => array( 'type' => 'custom', 'value' => $preset->input_border ?: '#E5E7EB' ),
                            'placeholder'  => ( isset( $bot->language ) && 'he' !== $bot->language ) ? 'Type a message...' : 'הקלד הודעה...',
                            'sendButtonBackground' => array( 'type' => 'global', 'ref' => 'colors.primary' ),
                        ),
                    );
                }
                
                $widget_settings = $preset->widget_settings;
                if ( is_string( $widget_settings ) ) {
                    $widget_settings = json_decode( $widget_settings, true );
                } elseif ( is_object( $widget_settings ) ) {
                    $widget_settings = json_decode( json_encode( $widget_settings ), true );
                }
                if ( ! is_array( $widget_settings ) ) {
                    $widget_settings = array();
                }
                
                // Resolve { type: 'default' } entries by inheriting from the default preset.
                // This must run BEFORE the config is assembled so the frontend only sees concrete values.
                if ( ! $preset->is_default && ( self::has_default_refs( $element_settings ) || self::has_default_refs( $widget_settings ) ) ) {
                    $default_preset = $preset_model->get_default_for_language( $bot->language ?? '' );
                    if ( $default_preset ) {
                        $default_element = $default_preset->element_settings;
                        if ( is_string( $default_element ) ) {
                            $default_element = json_decode( $default_element, true );
                        } elseif ( is_object( $default_element ) ) {
                            $default_element = json_decode( json_encode( $default_element ), true );
                        }
                        if ( ! is_array( $default_element ) ) {
                            $default_element = array();
                        }

                        $default_widget = $default_preset->widget_settings;
                        if ( is_string( $default_widget ) ) {
                            $default_widget = json_decode( $default_widget, true );
                        } elseif ( is_object( $default_widget ) ) {
                            $default_widget = json_decode( json_encode( $default_widget ), true );
                        }
                        if ( ! is_array( $default_widget ) ) {
                            $default_widget = array();
                        }

                        $element_settings = self::resolve_default_refs( $element_settings, $default_element );
                        $widget_settings  = self::resolve_default_refs( $widget_settings, $default_widget );
                    }
                }

                $config['appearance_preset'] = array(
                    'id'              => (int) $preset->id,
                    'name'            => $preset->name,
                    'global_values'   => $global_values,
                    'element_settings'=> $element_settings,
                    'widget_settings' => $widget_settings,
                    // Include legacy fields for backwards compatibility
                    'font_family'     => $preset->font_family ?? null,
                );
                
            // Override primary color from preset if set
            if ( isset( $global_values['colors']['primary'] ) && ! empty( $global_values['colors']['primary'] ) ) {
                $config['primary_color'] = $global_values['colors']['primary'];
            }
        }
        
        // Apply bot-specific appearance overrides if any
        if ( ! empty( $bot->appearance_overrides ) ) {
            $overrides = $bot->appearance_overrides;
            if ( is_string( $overrides ) ) {
                $overrides = json_decode( $overrides, true );
            }
            if ( is_array( $overrides ) ) {
                $config['appearance_overrides'] = $overrides;
            }
        }
        
        return $config;
    }

    /**
     * Check if an array tree contains any { type: 'default' } entries.
     *
     * @param array $data The data to scan.
     * @return bool True if at least one default ref is found.
     */
    private static function has_default_refs( $data ) {
        if ( ! is_array( $data ) ) {
            return false;
        }
        if ( isset( $data['type'] ) && 'default' === $data['type'] ) {
            return true;
        }
        foreach ( $data as $value ) {
            if ( is_array( $value ) && self::has_default_refs( $value ) ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Recursively replace { type: 'default' } entries with the corresponding
     * value from the default preset at the same path.
     *
     * @param array $data   The current preset's settings (element or widget).
     * @param array $source The default preset's settings at the same level.
     * @return array Resolved data with no 'default' refs remaining.
     */
    private static function resolve_default_refs( $data, $source ) {
        if ( ! is_array( $data ) ) {
            return $data;
        }

        // Leaf FieldValue node: { type: 'default' }
        if ( isset( $data['type'] ) && 'default' === $data['type'] && count( $data ) <= 2 ) {
            if ( is_array( $source ) && ! empty( $source ) ) {
                return $source;
            }
            return $data;
        }

        foreach ( $data as $key => $value ) {
            if ( ! is_array( $value ) ) {
                continue;
            }
            $source_child = isset( $source[ $key ] ) && is_array( $source[ $key ] ) ? $source[ $key ] : array();
            $data[ $key ] = self::resolve_default_refs( $value, $source_child );
        }

        return $data;
    }
}

