<?php
/**
 * Prompt Builder
 *
 * Builds system prompts for Claude based on site context and settings.
 *
 * @package BoostBot
 * @since 1.0.0
 */

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

/**
 * Class BoostBot_Prompt_Builder
 *
 * Creates contextual system prompts for the AI.
 */
class BoostBot_Prompt_Builder {
    /**
     * Numeric per-section caps and global budget have been REMOVED.
     *
     * Gemini's input tokens are our cheapest resource. We now send the full
     * content of every section and rely on text-based priority descriptions
     * within each section header to guide the AI on how to weigh information.
     *
     * @since 1.8.0 (caps removed in brand-card-tables update)
     */

    /**
     * Truncate text deterministically to a max length (word boundary when possible).
     *
     * @since 1.8.0
     */
    private function truncate_text( string $text, int $max_chars ): string {
        $text = (string) $text;
        if ( $max_chars <= 0 ) {
            return '';
        }
        if ( mb_strlen( $text ) <= $max_chars ) {
            return $text;
        }

        $truncated  = mb_substr( $text, 0, $max_chars );
        $last_space = mb_strrpos( $truncated, ' ' );
        if ( false !== $last_space && $last_space > (int) ( $max_chars * 0.7 ) ) {
            $truncated = mb_substr( $truncated, 0, $last_space );
        }
        return rtrim( $truncated ) . "\n...[truncated]";
    }

    /**
     * Add a section to the prompt. No per-section caps — full content is sent.
     *
     * @since 1.8.0
     */
    private function add_section( array &$sections, string $key, string $text ): void {
        $text = trim( (string) $text );
        if ( '' === $text ) {
            return;
        }
        $sections[] = array(
            'key'  => $key,
            'text' => $text,
        );
    }

    /**
     * Render final prompt from all sections. No global cap — full content is sent.
     * Each section carries its own text-based priority description for the AI.
     *
     * @since 1.8.0
     */
    private function render_prompt( array $sections ): string {
        $parts = array();
        foreach ( $sections as $s ) {
            if ( isset( $s['text'] ) && is_string( $s['text'] ) && '' !== trim( $s['text'] ) ) {
                $parts[] = $s['text'];
            }
        }
        return implode( "\n\n", $parts );
    }

    /**
     * Site profile data.
     *
     * @var array
     */
    private $site_profile;

    /**
     * Plugin settings.
     *
     * @var array
     */
    private $settings;

    /**
     * Active language for the current prompt build (set in build_system_prompt).
     *
     * @var string
     */
    private string $language = '';

    /**
     * Constructor.
     */
    public function __construct() {
        $this->site_profile = $this->get_site_profile();
        $this->settings     = $this->get_settings();
    }

    /**
     * Build the complete system prompt.
     *
     * Enhanced in Week 24 to include global context (service catalog).
     *
     * @param array $context Additional context (page_content, knowledge, global_context, bot).
     * @return string The system prompt.
     */
    public function build_system_prompt( array $context = array() ): string {
        $sections = array();

        // Determine the language for this prompt build
        if ( ! empty( $context['bot'] ) ) {
            $bot_obj = (object) $context['bot'];
            $this->language = $bot_obj->language ?? BoostBot_Language::get_primary();
        }

        $routing = isset( $context['routing'] ) && is_array( $context['routing'] ) ? $context['routing'] : array();
        $include_contact_list = isset( $routing['include_contact_list'] ) ? (bool) $routing['include_contact_list'] : true;
        $include_actions      = isset( $routing['include_actions'] ) ? (bool) $routing['include_actions'] : true;
        $include_page_context = isset( $routing['include_page_context'] ) ? (bool) $routing['include_page_context'] : true;

        // Week 27.8-2: tool selection results (Gemini). If present, avoid dumping lists/catalogs.
        $tool_selection = isset( $context['tool_selection'] ) && is_array( $context['tool_selection'] ) ? $context['tool_selection'] : array();
        $tool_sel_ok = isset( $tool_selection['ok'] ) ? (bool) $tool_selection['ok'] : false;
        $selected_contact_ids = isset( $tool_selection['selected_contact_ids'] ) && is_array( $tool_selection['selected_contact_ids'] )
            ? array_values( array_filter( array_map( 'absint', $tool_selection['selected_contact_ids'] ) ) )
            : array();
        $selected_widget_ids = isset( $tool_selection['selected_widget_ids'] ) && is_array( $tool_selection['selected_widget_ids'] )
            ? array_values( array_filter( array_map( 'absint', $tool_selection['selected_widget_ids'] ) ) )
            : array();
        $picker_widget_ids = isset( $tool_selection['picker_widget_ids'] ) && is_array( $tool_selection['picker_widget_ids'] )
            ? array_values( array_filter( array_map( 'absint', $tool_selection['picker_widget_ids'] ) ) )
            : array();

        // Personality prompt (if bot has personality_id, use it; otherwise use default)
        $personality_prompt = $this->get_personality_prompt( $context['bot'] ?? null );
        if ( ! empty( $personality_prompt ) ) {
            $this->add_section( $sections, 'personality', $personality_prompt );
        }

        // Base prompt (always included) - use bot data if available
        $this->add_section( $sections, 'base', $this->get_base_prompt( $context['bot'] ?? null ) );

        // Site context
        $this->add_section( $sections, 'site_context', $this->get_site_context() );

        // Global ServiceCatalog context disabled (removed): do not inject into prompt.

        // Week 27.6: BrandCard (compact stable identity). Included when provided.
        if ( ! empty( $context['brand_card'] ) ) {
            $this->add_section( $sections, 'brand_card', $this->get_brand_card_section( (string) $context['brand_card'] ) );
        }

        // Bot-specific instructions (rules, forbidden topics, custom instructions)
        if ( ! empty( $context['bot'] ) ) {
            $bot_instructions = $this->get_bot_instructions( $context['bot'] );
            if ( ! empty( $bot_instructions ) ) {
                $this->add_section( $sections, 'bot_rules', $bot_instructions );
            }
        }

        // Contacts: sourced from Contact Card widgets (not per-bot contact_list_id).
        // contact_to_widget_map maps contact_id -> widget_id (built by chat handler).
        $contact_to_widget_map = isset( $context['contact_to_widget_map'] ) && is_array( $context['contact_to_widget_map'] )
            ? $context['contact_to_widget_map']
            : array();

        if ( $include_contact_list ) {
            if ( $tool_sel_ok ) {
                $contact_hints = $this->get_contact_hints_prompt( $context['bot'] ?? null, $selected_contact_ids, $contact_to_widget_map );
                if ( '' !== trim( $contact_hints ) ) {
                    $this->add_section( $sections, 'contact_hints', $contact_hints );
                }
            } else {
                // Fail-soft: fall back to the existing (capped) contact list section.
                $contact_list = $this->get_contact_list_prompt( $context['bot'] ?? null, $contact_to_widget_map );
                if ( ! empty( $contact_list ) ) {
                    $this->add_section( $sections, 'contact_list', $contact_list );
                }
            }
        }

        // Behavioral instructions
        $this->add_section( $sections, 'behavior', $this->get_behavioral_instructions() );

        // Widgets/actions (Week 27.8-2): if tool selection succeeded, use hints and skip the full catalog.
        if ( $include_actions ) {
            if ( $tool_sel_ok ) {
                $widget_hints = $this->get_widget_hints_prompt( $context['bot'] ?? null, $selected_widget_ids, $picker_widget_ids );
                if ( '' !== trim( $widget_hints ) ) {
                    $this->add_section( $sections, 'widget_hints', $widget_hints );
                }
            } else {
                // Fail-soft: fall back to existing actions catalog.
                $actions = $this->get_available_actions( $context['bot'] ?? null );
                if ( ! empty( $actions ) ) {
                    $this->add_section( $sections, 'actions', $actions );
                }
            }
        }

        // WooCommerce product card instructions (if WooCommerce is active) - Week 27.8: gate with actions.
        if ( $include_actions ) {
            $woo_product_instructions = $this->get_woocommerce_product_instructions();
            if ( ! empty( $woo_product_instructions ) ) {
                $this->add_section( $sections, 'woocommerce', $woo_product_instructions );
            }
        }

        // Page context (if provided) - Week 27.8: gate and keep it small/structured.
        if ( $include_page_context && ! empty( $context['page_content'] ) ) {
            $this->add_section( $sections, 'page_context', $this->get_page_context( $context['page_content'] ) );
        }

        // Knowledge context (if provided)
        if ( ! empty( $context['knowledge'] ) ) {
            $this->add_section( $sections, 'knowledge', $this->get_knowledge_context( $context['knowledge'] ) );
        }

        // Response format instructions
        $this->add_section( $sections, 'response_format', $this->get_response_format() );

        // Template instructions (Week 21 - Pre-built Templates)
        $template_instructions = $this->get_template_instructions();
        if ( ! empty( $template_instructions ) ) {
            $this->add_section( $sections, 'templates', $template_instructions );
        }

        return $this->render_prompt( $sections );
    }

    /**
     * Week 27.8-2: Contacts hints section (do not dump full list).
     *
     * @param object|array|null $bot
     * @param int[]             $selected_contact_ids
     * @return string
     */
    /**
     * Contact hints section (tool selection succeeded).
     *
     * Contacts are grouped by their parent Contact Card widget so the AI
     * uses the token format: [WIDGET:contact_card:WIDGET_ID:CONTACT_ID]
     *
     * @param object|array|null $bot
     * @param int[]             $selected_contact_ids
     * @param array<int,int>    $contact_to_widget_map  contact_id => widget_id
     * @return string
     */
    private function get_contact_hints_prompt( $bot = null, array $selected_contact_ids = array(), array $contact_to_widget_map = array() ): string {
        $selected_contact_ids = array_values( array_filter( array_map( 'absint', $selected_contact_ids ) ) );
        $selected_contact_ids = array_slice( array_unique( $selected_contact_ids ), 0, 2 );
        $is_he = ( 'he' === $this->language );

        $prompt  = $is_he ? "### אנשי קשר\n" : "### Contact Cards\n";
        $prompt .= "PRIORITY: Use when the customer asks to contact someone or speak with a representative.\n";
        $prompt .= "These are pre-selected contacts relevant to this conversation. Only suggest them when the user explicitly asks for contact options.\n\n";
        $prompt .= $is_he
            ? "כאשר הלקוח מבקש ליצור קשר / לדבר עם נציג — השתמש בכרטיס איש קשר והוסף בסוף התשובה.\n"
            : "When the customer asks to get in touch or speak with a representative — use a contact card and add it at the end of your response.\n";
        $prompt .= "**" . ( $is_he ? 'פורמט' : 'Format' ) . ":** `[WIDGET:contact_card:<WIDGET_ID>:<CONTACT_ID>]`\n\n";

        if ( empty( $selected_contact_ids ) ) {
            $prompt .= $is_he
                ? "לא נבחר איש קשר ספציפי לשיחה הזו. אם צריך — שאל שאלה קצרה כדי לבחור תפקיד/מחלקה (מכירות/תמיכה/ניהול).\n"
                : "No specific contact was selected for this conversation. If needed — ask a short question to determine the role/department (sales/support/management).\n";
            return $prompt;
        }

        if ( class_exists( 'BoostBot_Contact_Model' ) ) {
            $contact_model = new BoostBot_Contact_Model();

            $prompt .= '**' . ( $is_he ? 'אנשי קשר מומלצים לשיחה הזו' : 'Recommended contacts for this conversation' ) . ":**\n";
            foreach ( $selected_contact_ids as $cid ) {
                $c = $contact_model->get( $cid );
                $wid = isset( $contact_to_widget_map[ $cid ] ) ? (int) $contact_to_widget_map[ $cid ] : 0;
                if ( ! $c ) {
                    $prompt .= "- Contact ID {$cid} (widget {$wid})\n";
                    continue;
                }
                $token = $wid > 0 ? "[WIDGET:contact_card:{$wid}:{$cid}]" : "[WIDGET:contact_card:{$cid}]";
                $line  = "- {$token}: **" . (string) ( $c->name ?? '' ) . '**';
                if ( ! empty( $c->title ) ) {
                    $label = $is_he ? 'תפקיד' : 'Role';
                    $line .= " ({$label}: " . (string) $c->title . ')';
                }
                if ( ! empty( $c->department ) ) {
                    $label = $is_he ? 'מחלקה' : 'Department';
                    $line .= " [{$label}: " . (string) $c->department . ']';
                }
                $prompt .= $line . "\n";
            }
            $prompt .= "\n";
        } else {
            $prompt .= '**' . ( $is_he ? 'אנשי קשר מומלצים לשיחה הזו (IDs)' : 'Recommended contacts for this conversation (IDs)' ) . ':** ' . implode( ', ', $selected_contact_ids ) . "\n\n";
        }

        $first_cid = (int) $selected_contact_ids[0];
        $first_wid = isset( $contact_to_widget_map[ $first_cid ] ) ? (int) $contact_to_widget_map[ $first_cid ] : 0;
        $example_token = $first_wid > 0 ? "[WIDGET:contact_card:{$first_wid}:{$first_cid}]" : "[WIDGET:contact_card:{$first_cid}]";

        if ( $is_he ) {
            $prompt .= "**דוגמה שימוש:**\n";
            $prompt .= "לקוח: \"אני רוצה לדבר עם נציג\"\n";
            $prompt .= "תשובה: \"בשמחה—הנה פרטי איש הקשר: {$example_token}\"\n";
        } else {
            $prompt .= "**Usage example:**\n";
            $prompt .= "Customer: \"I'd like to speak with a representative\"\n";
            $prompt .= "Response: \"Of course — here are the contact details: {$example_token}\"\n";
        }

        return $prompt;
    }

    /**
     * Week 27.8-2: Widget hints section (do not dump full catalog).
     *
     * @param object|array|null $bot
     * @param int[]             $selected_widget_ids
     * @param int[]             $picker_widget_ids
     * @return string
     */
    private function get_widget_hints_prompt( $bot = null, array $selected_widget_ids = array(), array $picker_widget_ids = array() ): string {
        $selected_widget_ids = array_slice( array_unique( array_values( array_filter( array_map( 'absint', $selected_widget_ids ) ) ) ), 0, 3 );
        $picker_widget_ids   = array_slice( array_unique( array_values( array_filter( array_map( 'absint', $picker_widget_ids ) ) ) ), 0, 5 );
        $is_he = ( 'he' === $this->language );

        $widgets = $this->get_bot_widgets( $bot );
        $by_id = array();
        foreach ( (array) $widgets as $w ) {
            $w = (object) $w;
            if ( empty( $w->id ) ) {
                continue;
            }
            $by_id[ (int) $w->id ] = $w;
        }

        $prompt  = $is_he ? "### וידג'טים — בחירה חכמה\n" : "### Widgets — Smart Selection\n";
        $prompt .= "PRIORITY: Use when the conversation naturally calls for an interactive element.\n";
        $prompt .= "Only suggest widgets when the user's request clearly matches one. Do not aggressively push widgets.\n\n";

        if ( $is_he ) {
            $prompt .= "**⭐ כללי סקר שביעות רצון (survey):**\n";
            $prompt .= "- הצג סקר **רק** כשהלקוח מרוצה ואין לו שאלות/צרכים נוספים (\"תודה\", \"עזרת לי\", \"זה הכל\")\n";
            $prompt .= "- **לעולם אל** תציג סקר באמצע שיחה פעילה או כשיש בעיה לא פתורה\n\n";
            $prompt .= "**📇 שמירת ליד אוטומטית (LEAD_SAVE):**\n";
            $prompt .= "כשהלקוח משתף **שם + טלפון** — שמור את הפרטים:\n";
            $prompt .= "`[LEAD_SAVE:name=שם|phone=טלפון|summary=תיאור קצר של השיחה]`\n";
            $prompt .= "- חובה: שם + טלפון. אם יש רק טלפון — שאל לשמו לפני השמירה\n";
            $prompt .= "- אופציונלי: email=כתובת, summary=תיאור\n";
            $prompt .= "- שלח LEAD_SAVE **פעם אחת בלבד** בשיחה\n";
            $prompt .= "- הטוקן יוסר ולא יוצג ללקוח\n";
            $prompt .= "- **חשוב**: אם טופס עם שדה טלפון כבר מוצג למשתמש (צורף כווידג'ט מראש), **בקש מהמשתמש להשלים ולשלוח את הטופס** במקום להשתמש ב-LEAD_SAVE\n\n";
            $prompt .= "כדי להפעיל וידג'ט, הוסף את הטוקן בסוף התשובה (בדיוק כמו שהוא):\n\n";
        } else {
            $prompt .= "**⭐ Satisfaction survey rules (survey):**\n";
            $prompt .= "- Show a survey **only** when the customer is satisfied and has no further questions/needs (\"thanks\", \"you helped me\", \"that's all\")\n";
            $prompt .= "- **Never** show a survey in the middle of an active conversation or when there is an unresolved issue\n\n";
            $prompt .= "**📇 Automatic lead saving (LEAD_SAVE):**\n";
            $prompt .= "When the customer shares their **name + phone number** — save the details:\n";
            $prompt .= "`[LEAD_SAVE:name=Customer Name|phone=Phone Number|summary=Brief description of the conversation]`\n";
            $prompt .= "- Required: name + phone. If only a phone number is provided — ask for their name before saving\n";
            $prompt .= "- Optional: email=address, summary=description\n";
            $prompt .= "- Send LEAD_SAVE **only once** per conversation\n";
            $prompt .= "- The token will be removed and not shown to the customer\n";
            $prompt .= "- **Important**: If a form with a phone field is already displayed to the user (attached as a widget in advance), **ask the user to complete and submit the form** instead of using LEAD_SAVE\n\n";
            $prompt .= "To activate a widget, add the token at the end of your response (exactly as shown):\n\n";
        }

        if ( ! empty( $selected_widget_ids ) ) {
            $prompt .= '**' . ( $is_he ? 'וידג\'טים מומלצים לשיחה הזו' : 'Recommended widgets for this conversation' ) . ":**\n";
            foreach ( $selected_widget_ids as $id ) {
                $w = $by_id[ $id ] ?? null;
                if ( ! $w ) {
                    $prompt .= "- ID {$id}\n";
                    continue;
                }
                $type = (string) ( $w->type ?? '' );
                $name = (string) ( $w->name ?? '' );
                $desc = (string) ( $w->description ?? '' );
                if ( empty( $desc ) && class_exists( 'BoostBot_Widget_Model' ) ) {
                    $desc = BoostBot_Widget_Model::get_default_bot_description( $type );
                }
                $token = "[WIDGET:{$type}:{$id}]";
                $hint_line = "- {$token} — {$name}";
                if ( ! empty( $desc ) ) {
                    $hint_line .= ": {$desc}";
                }
                $prompt .= "{$hint_line}\n";
            }
            $prompt .= "\n";
        }

        if ( ! empty( $picker_widget_ids ) ) {
            $pairs = array();
            foreach ( $picker_widget_ids as $id ) {
                $w = $by_id[ $id ] ?? null;
                if ( ! $w ) {
                    continue;
                }
                $type = (string) ( $w->type ?? '' );
                if ( '' === $type ) {
                    continue;
                }
                $pairs[] = $type . ':' . (string) $id;
            }
            if ( ! empty( $pairs ) ) {
                $prompt .= $is_he
                    ? "**אם יש כמה אפשרויות מתאימות — תן למשתמש לבחור:**\n"
                    : "**If there are multiple suitable options — let the user choose:**\n";
                $prompt .= ( $is_he ? 'השתמש בטוקן' : 'Use the token' ) . ": [WIDGET_PICKER:" . implode( ',', $pairs ) . "]\n\n";
            }
        }

        if ( empty( $selected_widget_ids ) && empty( $picker_widget_ids ) ) {
            $prompt .= $is_he
                ? "לא נבחר ווידג'ט ספציפי לשיחה הזו.\n"
                : "No specific widget was selected for this conversation.\n";
        }

        return $prompt;
    }

    // NOTE: ServiceCatalog prompt section removed (Week 27.7 rollback).

    /**
     * Week 27.6: BrandCard section (compact, stable formatting for caching).
     *
     * @param string $brand_card Preformatted brand card text.
     * @return string
     */
    private function get_brand_card_section( string $brand_card ): string {
        $is_he = ( 'he' === $this->language );
        $header = $is_he ? '### כרטיס העסק' : '### Business Information';

        return "{$header}\n"
            . "PRIORITY: This is the business information that the website owner has provided.\n"
            . "This content is important for general context of the company as we provide answers\n"
            . "to customers in almost any subject. Always consider this information when discussing\n"
            . "the business, its services, products, contact details, or general company facts.\n"
            . "This is your most reliable source of company identity. It may include structured\n"
            . "tables with business data — use them as authoritative reference.\n\n"
            . "{$brand_card}";
    }

    /**
     * Get personality prompt for the bot.
     *
     * @param object|array|null $bot Bot data.
     * @return string Personality prompt.
     */
    private function get_personality_prompt( $bot = null ): string {
        // Check if personality model exists
        if ( ! class_exists( 'BoostBot_Personality_Model' ) ) {
            return '';
        }

        $personality_model = new BoostBot_Personality_Model();
        $personality       = null;

        // Try to get personality from bot
        if ( $bot ) {
            $bot = (object) $bot;
            if ( ! empty( $bot->personality_id ) ) {
                $personality = $personality_model->get( $bot->personality_id );
            }
        }

        // Fall back to default personality
        if ( ! $personality ) {
            $personality = $personality_model->get_default();
        }

        if ( ! $personality || empty( $personality->system_prompt ) ) {
            return '';
        }

        $is_he = ( 'he' === $this->language );

        $header = $is_he ? '### אישיות הבוט' : '### Bot Personality';
        $prompt = "{$header}\nPRIORITY: MUST FOLLOW. This defines the bot's personality, tone, and communication style as set by the business owner. Always maintain this personality throughout the conversation.\n\n" . $personality->system_prompt;

        $behavior_instructions = array();
        
        if ( ! empty( $personality->proactive_engagement ) ) {
            if ( $is_he ) {
                $behavior_instructions[] = "- היה יוזם ופרואקטיבי - הצע עזרה גם כשלא שואלים ישירות";
                $behavior_instructions[] = "- אם הלקוח מהסס - עזור לו להתקדם עם שאלות מנחות";
            } else {
                $behavior_instructions[] = "- Be proactive — offer help even when not directly asked";
                $behavior_instructions[] = "- If the customer hesitates — help them move forward with guiding questions";
            }
        }
        
        if ( ! empty( $behavior_instructions ) ) {
            $section_label = $is_he ? '### התנהגות מיוחדת:' : '### Special Behavior:';
            $prompt .= "\n\n{$section_label}\n" . implode( "\n", $behavior_instructions );
        }

        if ( ! empty( $personality->traits ) ) {
            $traits = is_string( $personality->traits )
                ? json_decode( $personality->traits, true )
                : $personality->traits;

            if ( is_array( $traits ) && ! empty( $traits ) && class_exists( 'BoostBot_Personality_Trait_Model' ) ) {
                $trait_model = new BoostBot_Personality_Trait_Model();
                $trait_prompt = $trait_model->get_combined_prompt( $traits );
                if ( ! empty( $trait_prompt ) ) {
                    $prompt .= "\n" . $trait_prompt;
                }
            }
        }

        if ( $bot ) {
            $bot = (object) $bot;
            if ( ! empty( $bot->personality_traits ) ) {
                $bot_traits = is_string( $bot->personality_traits )
                    ? json_decode( $bot->personality_traits, true )
                    : $bot->personality_traits;

                if ( is_array( $bot_traits ) && ! empty( $bot_traits ) && class_exists( 'BoostBot_Personality_Trait_Model' ) ) {
                    $trait_model = new BoostBot_Personality_Trait_Model();
                    $bot_trait_prompt = $trait_model->get_combined_prompt( $bot_traits );
                    if ( ! empty( $bot_trait_prompt ) ) {
                        $extra_label = $is_he ? '### תגיות אישיות נוספות' : '### Additional Personality Traits';
                        $prompt .= "\n\n{$extra_label}" . $bot_trait_prompt;
                    }
                }
            }
        }

        return $prompt;
    }

    /**
     * Contact list prompt (fail-soft fallback when tool selection didn't run).
     *
     * Contacts are sourced from Contact Card widgets attached to the bot.
     * Grouped by widget so the AI uses: [WIDGET:contact_card:WIDGET_ID:CONTACT_ID]
     *
     * @param object|array|null $bot
     * @param array<int,int>    $contact_to_widget_map  contact_id => widget_id
     * @return string
     */
    private function get_contact_list_prompt( $bot = null, array $contact_to_widget_map = array() ): string {
        if ( empty( $bot ) || empty( $bot->id ) ) {
            return $this->get_fallback_contact_instructions();
        }

        if ( ! class_exists( 'BoostBot_Contact_Model' ) || ! class_exists( 'BoostBot_Widget_Model' ) ) {
            return $this->get_fallback_contact_instructions();
        }

        $bot = (object) $bot;

        // Load contact_card widgets for this bot.
        $widget_model = new BoostBot_Widget_Model();
        $widgets      = $widget_model->get_for_chatbot( absint( $bot->id ) );
        if ( empty( $widgets ) || ! is_array( $widgets ) ) {
            return $this->get_fallback_contact_instructions();
        }

        $contact_model    = new BoostBot_Contact_Model();
        $widget_contacts  = array(); // widget_id => [ contact, ... ]

        foreach ( $widgets as $w ) {
            $w = (object) $w;
            if ( 'contact_card' !== ( $w->type ?? '' ) ) {
                continue;
            }
            $wid              = absint( $w->id ?? 0 );
            $config           = is_string( $w->config ?? '' ) ? json_decode( $w->config, true ) : ( $w->config ?? array() );
            $contact_list_ids = $config['contact_list_ids'] ?? array();
            if ( empty( $contact_list_ids ) || ! is_array( $contact_list_ids ) ) {
                continue;
            }
            $seen = array();
            foreach ( $contact_list_ids as $list_id ) {
                $lc = $contact_model->get_by_list( absint( $list_id ) );
                if ( empty( $lc ) || ! is_array( $lc ) ) {
                    continue;
                }
                foreach ( $lc as $c ) {
                    $c   = (object) $c;
                    $cid = absint( $c->id ?? 0 );
                    if ( $cid <= 0 || in_array( $cid, $seen, true ) ) {
                        continue;
                    }
                    $seen[] = $cid;
                    if ( ! isset( $widget_contacts[ $wid ] ) ) {
                        $widget_contacts[ $wid ] = array();
                    }
                    $widget_contacts[ $wid ][] = $c;
                }
            }
        }

        if ( empty( $widget_contacts ) ) {
            return $this->get_fallback_contact_instructions();
        }

        $is_he = ( 'he' === $this->language );

        $prompt  = $is_he ? "### אנשי קשר\n" : "### Contact List\n";
        $prompt .= "PRIORITY: Use when the customer asks to contact someone, speak with a representative, or asks about communication channels. Only show contact cards when explicitly requested by the user.\n\n";
        $prompt .= $is_he
            ? "כאשר הלקוח מבקש לדבר עם נציג, איש קשר, או שואל על דרכי התקשרות - **חובה להשתמש בכרטיס איש קשר!**\n\n"
            : "When the customer asks to speak with a representative, a contact person, or asks about communication channels — **you must use a contact card!**\n\n";
        $prompt .= "**" . ( $is_he ? 'פורמט' : 'Format' ) . ":** `[WIDGET:contact_card:<WIDGET_ID>:<CONTACT_ID>]`\n\n";

        $first_example_token = '';

        foreach ( $widget_contacts as $wid => $contacts ) {
            $preview = array_slice( $contacts, 0, 3 );
            $prompt .= '**' . ( $is_he ? "כרטיס איש קשר (Widget #{$wid})" : "Contact Card (Widget #{$wid})" ) . ":**\n";
            foreach ( $preview as $c ) {
                $cid = absint( $c->id ?? 0 );
                $token = "[WIDGET:contact_card:{$wid}:{$cid}]";
                if ( empty( $first_example_token ) ) {
                    $first_example_token = $token;
                }
                $prompt .= "  - {$token}: **{$c->name}**";
                if ( ! empty( $c->title ) ) {
                    $label = $is_he ? 'תפקיד' : 'Role';
                    $prompt .= " ({$label}: {$c->title})";
                }
                if ( ! empty( $c->department ) ) {
                    $label = $is_he ? 'מחלקה' : 'Department';
                    $prompt .= " [{$label}: {$c->department}]";
                }
                $prompt .= "\n";
            }
            if ( count( $contacts ) > count( $preview ) ) {
                $remaining = count( $contacts ) - count( $preview );
                $prompt .= $is_he
                    ? "  - ... ועוד {$remaining} אנשי קשר\n"
                    : "  - ... and {$remaining} more contacts\n";
            }
            $prompt .= "\n";
        }

        if ( ! empty( $first_example_token ) ) {
            if ( $is_he ) {
                $prompt .= "**דוגמאות שימוש:**\n";
                $prompt .= "לקוח: \"אני רוצה לדבר עם נציג\"\n";
                $prompt .= "תשובה: \"בטח! הנה פרטי איש הקשר שלנו: {$first_example_token}\"\n\n";
            } else {
                $prompt .= "**Usage examples:**\n";
                $prompt .= "Customer: \"I'd like to speak with a representative\"\n";
                $prompt .= "Response: \"Sure! Here are our contact details: {$first_example_token}\"\n\n";
            }
        }

        if ( $is_he ) {
            $prompt .= "**מתי להציג כרטיס:**\n";
            $prompt .= "- כשלקוח מבקש ליצור קשר או לדבר עם נציג\n";
            $prompt .= "- כששואל על טלפון/מייל/וואטסאפ\n";
            $prompt .= "- כשרוצה עזרה אישית מנציג\n";
            $prompt .= "- כשמזכיר תפקיד ספציפי (מנהל, תמיכה, מכירות וכו')\n\n";
            $prompt .= "**שים לב:** תמיד הוסף את הטוקן [WIDGET:contact_card:WIDGET_ID:CONTACT_ID] בסוף התשובה!";
        } else {
            $prompt .= "**When to show a contact card:**\n";
            $prompt .= "- When a customer asks to get in touch or speak with a representative\n";
            $prompt .= "- When asking about phone/email/WhatsApp\n";
            $prompt .= "- When they want personal assistance from a representative\n";
            $prompt .= "- When they mention a specific role (manager, support, sales, etc.)\n\n";
            $prompt .= "**Note:** Always add the token [WIDGET:contact_card:WIDGET_ID:CONTACT_ID] at the end of your response!";
        }

        return $prompt;
    }

    /**
     * Get bot-specific instructions (custom instructions + global rules/forbidden topics).
     *
     * @param object|array $bot Bot data.
     * @return string Bot instructions section.
     */
    private function get_bot_instructions( $bot ): string {
        $bot = (object) $bot;
        $sections = array();

        $is_en = ( 'he' !== $this->language );

        $sections[] = "### Bot Rules & Instructions\nPRIORITY: These are explicit rules set by the business owner. They MUST be followed at all times, overriding default behavior when there is a conflict.\n";

        if ( ! empty( $bot->custom_instructions ) ) {
            $label = $is_en ? '**Custom instructions:**' : '**הנחיות מותאמות:**';
            $sections[] = "{$label}\n{$bot->custom_instructions}";
        }

        // Per-language global rules.
        $global_rules = '';
        if ( class_exists( 'BoostBot_Settings' ) ) {
            $global_rules = BoostBot_Settings::get_for_language( 'global_rules', $this->language, '' );
        }
        if ( ! empty( $global_rules ) ) {
            $sections[] = "**Global rules (MUST FOLLOW):**\n{$global_rules}";
        }

        // Per-language global forbidden topics.
        $global_forbidden = '';
        if ( class_exists( 'BoostBot_Settings' ) ) {
            $global_forbidden = BoostBot_Settings::get_for_language( 'global_forbidden_topics', $this->language, '' );
        }
        if ( ! empty( $global_forbidden ) ) {
            $sections[] = "**Forbidden topics (NEVER discuss these):**\n{$global_forbidden}";
        }

        return implode( "\n\n", $sections );
    }

    /**
     * Get base prompt.
     *
     * @param object|array|null $bot Bot data.
     * @return string
     */
    private function get_base_prompt( $bot = null ): string {
        $language = BoostBot_Language::get_primary();
        if ( $bot ) {
            $bot = (object) $bot;
            $language = $bot->language ?? BoostBot_Language::get_primary();
        }

        return 'en' === $language
            ? $this->get_base_prompt_en( $bot )
            : $this->get_base_prompt_he( $bot );
    }

    private function get_base_prompt_he( $bot = null ): string {
        $bot_name      = $this->settings['bot_name'] ?? 'BoostBot';
        if ( $bot ) {
            $bot_name = ( (object) $bot )->bot_name ?? $bot_name;
        }
        $business_name = $this->site_profile['business_name'] ?? get_bloginfo( 'name' );
        $tone          = $this->settings['tone_of_voice'] ?? 'ידידותי ומקצועי';
        $claude_model  = BoostBot_Settings::get( 'claude_model', 'claude-3-5-sonnet-latest' );
        $model_name    = $this->get_model_display_name( $claude_model );

        return "### Core Identity & Instructions\n"
            . "PRIORITY: CRITICAL — MUST FOLLOW. This defines who you are, your fundamental behavior,\n"
            . "and the rules you must always follow. Never contradict these instructions.\n\n"
            . "אתה {$bot_name}, עוזר וירטואלי חכם ומקצועי מבית BoostBot.\n\n"
            . "### מי אתה\n"
            . "- אתה נציג שירות וירטואלי של {$business_name}\n"
            . "- אתה מופעל על ידי {$model_name} (מודל AI של Anthropic)\n"
            . "- אתה מדבר עברית שוטפת וטבעית\n"
            . "- אתה ידידותי, מקצועי ומועיל\n"
            . "- אתה עונה בתמציתיות אך בבהירות\n\n"
            . "### כללים חשובים\n"
            . "1. ענה תמיד בעברית (אלא אם נשאלת בשפה אחרת)\n"
            . "2. היה אדיב ומקצועי\n"
            . "3. אל תמציא מידע - אם אינך יודע, אמור זאת בכנות\n"
            . "4. הפנה לתוכן רלוונטי באתר כשאפשרי\n"
            . "5. נווט בעדינות לכיוון המרה (השארת פרטים, רכישה)\n"
            . "6. אל תציע הנחות או מחירים שלא אושרו\n"
            . "7. שמור על טון {$tone}\n"
            . "8. תשובות קצרות ותמציתיות (2-3 משפטים בדרך כלל)";
    }

    private function get_base_prompt_en( $bot = null ): string {
        $bot_name      = $this->settings['bot_name'] ?? 'BoostBot';
        if ( $bot ) {
            $bot_name = ( (object) $bot )->bot_name ?? $bot_name;
        }
        $business_name = $this->site_profile['business_name'] ?? get_bloginfo( 'name' );
        $tone          = $this->settings['tone_of_voice'] ?? 'friendly and professional';
        $claude_model  = BoostBot_Settings::get( 'claude_model', 'claude-3-5-sonnet-latest' );
        $model_name    = $this->get_model_display_name( $claude_model );

        return "### Core Identity & Instructions\n"
            . "PRIORITY: CRITICAL — MUST FOLLOW. This defines who you are, your fundamental behavior,\n"
            . "and the rules you must always follow. Never contradict these instructions.\n\n"
            . "You are {$bot_name}, a smart and professional virtual assistant powered by BoostBot.\n\n"
            . "### Who You Are\n"
            . "- You are a virtual customer service representative for {$business_name}\n"
            . "- You are powered by {$model_name} (an AI model by Anthropic)\n"
            . "- You speak fluent and natural English\n"
            . "- You are friendly, professional, and helpful\n"
            . "- You respond concisely yet clearly\n\n"
            . "### Important Rules\n"
            . "1. ALWAYS respond in English (unless asked in another language)\n"
            . "2. Be polite and professional\n"
            . "3. Never make up information — if you don't know, say so honestly\n"
            . "4. Refer to relevant content on the website when possible\n"
            . "5. Gently guide toward conversion (leaving details, purchase)\n"
            . "6. Do not offer discounts or prices that were not approved\n"
            . "7. Maintain a {$tone} tone\n"
            . "8. Keep responses short and concise (2-3 sentences usually)";
    }

    /**
     * Get site context section.
     *
     * @return string
     */
    private function get_site_context(): string {
        $business_name = $this->site_profile['business_name'] ?? get_bloginfo( 'name' );
        $business_type = $this->site_profile['business_type'] ?? '';
        $description   = $this->site_profile['business_description'] ?? get_bloginfo( 'description' );
        $topics        = $this->site_profile['main_topics'] ?? array();
        $goals         = $this->site_profile['conversion_goals'] ?? array( 'lead' );

        if ( 'en' === $this->language ) {
            $topics_str = ! empty( $topics ) ? implode( ', ', $topics ) : 'Not defined';
            $goals_str  = $this->format_goals( $goals );

            return "### Site Context\n"
                . "PRIORITY: Important background context. Use this to understand the business type,\n"
                . "goals, and main topics. This helps you tailor responses to the business domain.\n\n"
                . "### About the Business\n"
                . "- Business name: {$business_name}\n"
                . "- Industry: {$business_type}\n"
                . "- Description: {$description}\n"
                . "- Main topics: {$topics_str}\n\n"
                . "### Your Goals\n"
                . "{$goals_str}";
        }

        $topics_str = ! empty( $topics ) ? implode( ', ', $topics ) : 'לא הוגדרו';
        $goals_str  = $this->format_goals( $goals );

        return "### הקשר האתר\n"
            . "PRIORITY: Important background context. Use this to understand the business type,\n"
            . "goals, and main topics. This helps you tailor responses to the business domain.\n\n"
            . "### על העסק\n"
            . "- שם העסק: {$business_name}\n"
            . "- תחום: {$business_type}\n"
            . "- תיאור: {$description}\n"
            . "- נושאים עיקריים: {$topics_str}\n\n"
            . "### המטרות שלך\n"
            . "{$goals_str}";
    }

    /**
     * Get behavioral instructions.
     *
     * @return string
     */
    private function get_behavioral_instructions(): string {
        if ( 'en' === $this->language ) {
            return "### Conversation Behavior\n"
                . "PRIORITY: Important guidelines for how to conduct the conversation. Follow these\n"
                . "patterns for engagement, accuracy, and conversion. These shape the user experience.\n"
                . "\n"
                . "### Conversation Flow\n"
                . "1. **Opening**: Greet warmly if this is the first message\n"
                . "2. **Listening**: Understand the need before responding\n"
                . "3. **Helping**: Provide helpful and accurate information\n"
                . "4. **Guiding**: Suggest a clear next step\n"
                . "5. **Closing**: Encourage leaving details or taking action when relevant\n"
                . "\n"
                . "### Accuracy Rules (RAG) — Critical\n"
                . "- **NEVER make up numbers/prices/packages.** If you cite a price/amount/range — it **MUST** appear in the \"Relevant Site Content\" (Evidence Bundle) or in the \"Page Summary\".\n"
                . "- If you cannot find an exact price on the site: say so, ask a brief clarifying question (which service/which page), and suggest relevant links that were found.\n"
                . "- If multiple different price sets were found from different pages: do not guess. Prefer the page most relevant to the service asked about; if unclear — ask a brief question and show page titles/links.\n"
                . "\n"
                . "### When to Suggest Leaving Contact Details\n"
                . "- When the customer asks to get in touch\n"
                . "- When there is clear interest in a service\n"
                . "- When the customer asks about pricing\n"
                . "- When the conversation progresses and there is interest\n"
                . "\n"
                . "### What NOT to Do\n"
                . "- Do not make up information or prices\n"
                . "- Do not promise things beyond your authority\n"
                . "- Do not send external links (only to the website itself)\n"
                . "- Do not be intrusive or overly pushy";
        }

        return "### התנהגות בשיחה\n"
            . "PRIORITY: Important guidelines for how to conduct the conversation. Follow these\n"
            . "patterns for engagement, accuracy, and conversion. These shape the user experience.\n"
            . "\n"
            . "### התנהגות בשיחה\n"
            . "1. **פתיחה**: קבל את הפונה בחמימות אם זו הודעה ראשונה\n"
            . "2. **הקשבה**: הבן את הצורך לפני שאתה עונה\n"
            . "3. **עזרה**: ספק מידע מועיל ומדויק\n"
            . "4. **הכוונה**: הצע צעד הבא ברור\n"
            . "5. **סגירה**: עודד להשארת פרטים או לפעולה כשרלוונטי\n"
            . "\n"
            . "### כללי דיוק (RAG) — קריטי\n"
            . "- **אסור להמציא מספרים/מחירים/חבילות.** אם אתה מציין מחיר/סכום/טווח/מספר פריטים — הוא **חייב** להופיע ב-\"תוכן רלוונטי מהאתר\" (Evidence Bundle) או ב\"תמצית העמוד\".\n"
            . "- אם לא מצאת באתר מחיר מדויק: אמור זאת, בקש הבהרה קצרה (איזה שירות/איזה עמוד), והצע קישור(ים) רלוונטיים שכן נמצאו.\n"
            . "- אם נמצאו כמה סטים שונים של מחירים מכמה עמודים: אל תנחש. העדף את העמוד שהכי מתאים לשירות שנשאל; אם לא ברור — שאל שאלה קצרה והצג את כותרות העמודים/קישורים.\n"
            . "\n"
            . "### מתי להציע השארת פרטים\n"
            . "- כשהלקוח מבקש יצירת קשר\n"
            . "- כשיש התעניינות ברורה בשירות\n"
            . "- כשהלקוח שואל על מחירים\n"
            . "- כשהשיחה מתקדמת ויש עניין\n"
            . "\n"
            . "### מה לא לעשות\n"
            . "- לא להמציא מידע או מחירים\n"
            . "- לא להבטיח דברים שאינם בסמכותך\n"
            . "- לא לשלוח לינקים חיצוניים (רק לאתר עצמו)\n"
            . "- לא להיות פולשני או לוחץ מדי";
    }

    /**
     * Get available actions/widgets for the bot.
     *
     * @param object|null $bot Bot data.
     * @return string
     */
    private function get_available_actions( $bot = null ): string {
        $widgets = $this->get_bot_widgets( $bot );

        if ( empty( $widgets ) ) {
            return '';
        }

        $is_he = ( 'he' === $this->language );

        $actions  = array();
        $examples = array();
        $usage    = array();

        foreach ( $widgets as $widget ) {
            $widget_id   = $widget->id;
            $widget_name = $widget->name;
            $widget_type = $widget->type;
            $description = $widget->description ?? '';

            if ( empty( $description ) && class_exists( 'BoostBot_Widget_Model' ) ) {
                $description = BoostBot_Widget_Model::get_default_bot_description( $widget_type );
            }

            $token = "[WIDGET:{$widget_type}:{$widget_id}]";

            $action_line = "{$token} - {$widget_name}";
            if ( ! empty( $description ) ) {
                $action_line .= ": {$description}";
            }
            $actions[] = $action_line;

            if ( ! empty( $description ) ) {
                $usage[] = $is_he
                    ? "- ✅ {$description} → השתמש ב-{$token}"
                    : "- ✅ {$description} → use {$token}";
            }
        }

        $actions_str = implode( "\n", $actions );
        $usage_str   = ! empty( $usage ) ? implode( "\n", $usage ) : '';

        if ( $is_he ) {
            $prompt = "### וידג'טים זמינים\n"
                . "PRIORITY: These are interactive widgets you can suggest. Only suggest them when\n"
                . "the conversation naturally calls for it — do not push widgets aggressively.\n"
                . "Use the exact token format shown below at the end of your response.\n"
                . "\n"
                . "יש לך יכולת להציג וידג'טים אינטראקטיביים. כדי להפעיל וידג'ט, הוסף את הטוקן **בדיוק כמו שהוא** בסוף תשובתך:\n"
                . "\n"
                . "{$actions_str}\n\n";
        } else {
            $prompt = "### Available Widgets\n"
                . "PRIORITY: These are interactive widgets you can suggest. Only suggest them when\n"
                . "the conversation naturally calls for it — do not push widgets aggressively.\n"
                . "Use the exact token format shown below at the end of your response.\n"
                . "\n"
                . "You can display interactive widgets. To activate a widget, add the token **exactly as shown** at the end of your response:\n"
                . "\n"
                . "{$actions_str}\n\n";
        }

        if ( ! empty( $usage_str ) ) {
            $when_label = $is_he ? '**מתי להשתמש:**' : '**When to use:**';
            $prompt .= "\n{$when_label}\n{$usage_str}\n\n";
        }

        if ( $is_he ) {
            $prompt .= "\n**הוראות חשובות:**\n"
                . "- הטוקן צריך להיות בדיוק בפורמט הזה, בסוגריים מרובעים\n"
                . "- הוסף את הטוקן בסוף התשובה שלך\n"
                . "- השתמש בוידג'טים כשהלקוח מבקש או כשזה מתאים להקשר השיחה\n"
                . "- אל תשתמש בוידג'טים בצורה אגרסיבית או לא מתאימה\n"
                . "\n"
                . "**⭐ כללי סקר שביעות רצון (survey):**\n"
                . "- הצג סקר שביעות רצון **רק** כשהלקוח מרוצה מהשירות ואין לו שאלות או צרכים נוספים\n"
                . '- סימנים שהלקוח מוכן לסקר: "תודה", "עזרת לי", "מצוין", "אין לי עוד שאלות", "זה הכל"' . "\n"
                . "- **לעולם אל** תציג סקר באמצע שיחה פעילה, כשהלקוח עדיין שואל שאלות, או כשיש בעיה לא פתורה\n"
                . "- הסקר צריך להופיע בסוף שיחה מוצלחת בלבד, כמחווה טבעית של סיום\n"
                . "\n"
                . "**📇 שמירת ליד אוטומטית (LEAD_SAVE):**\n"
                . "כשאתה מזהה שהלקוח שיתף את **שמו ומספר הטלפון** שלו במהלך השיחה — שמור את הפרטים באמצעות טוקן:\n"
                . '`[LEAD_SAVE:name=שם הלקוח|phone=מספר טלפון|summary=תיאור קצר של השיחה והצרכים]`' . "\n"
                . "\n"
                . "**כללים:**\n"
                . "- **חובה**: שם + טלפון. אם יש רק טלפון — שאל את הלקוח לשמו לפני השמירה\n"
                . "- **אופציונלי**: email, summary\n"
                . "- ב-summary: תאר בקצרה את נושא השיחה וצרכי הלקוח (עד 100 מילים)\n"
                . "- הוסף את הטוקן בסוף התשובה שלך (הוא יוסר אוטומטית ולא יוצג ללקוח)\n"
                . "- אל תשמור אותו ליד פעמיים — אם כבר שלחת LEAD_SAVE בשיחה הזו, אל תשלח שוב\n"
                . "- אם הלקוח גם מבקש שירות (פגישה, טופס וכו') — שמור את הליד **בנוסף** לווידג'ט\n"
                . "- **חשוב**: אם טופס עם שדה טלפון כבר מוצג למשתמש (צורף כווידג'ט מראש עם ערכים מוזנים), **בקש מהמשתמש להשלים ולשלוח את הטופס** במקום להשתמש ב-LEAD_SAVE\n"
                . "\n"
                . "דוגמה:\n"
                . '- לקוח: "הי, אני יוסי, הטלפון שלי 050-1234567, אני מעוניין בחבילת הפרמיום"' . "\n"
                . '- תשובה: "שמח להכיר, יוסי! אשמח לספר לך על חבילת הפרמיום שלנו... [LEAD_SAVE:name=יוסי|phone=050-1234567|summary=הלקוח מתעניין בחבילת הפרמיום]"' . "\n"
                . "\n"
                . "**🔀 מתן אפשרות בחירה למשתמש:**\n"
                . 'כאשר הלקוח שואל שאלה כללית (כמו "איך אני יכול ליצור קשר?") ויש לך מספר וידג\'טים רלוונטיים, **תן לו לבחור** במקום להחליט בשבילו.' . "\n"
                . "השתמש בטוקן: [WIDGET_PICKER:type1:id1,type2:id2,type3:id3]\n"
                . "\n"
                . "דוגמה:\n"
                . '- לקוח: "איך אני יכול ליצור קשר?"' . "\n"
                . '- תשובה נכונה: "יש לי כמה אפשרויות בשבילך! איך תרצה ליצור קשר? [WIDGET_PICKER:form:1,whatsapp:2,calendar_meeting:3]"' . "\n"
                . "\n"
                . "הלקוח יראה כפתורי בחירה יפים ויוכל לבחור את הדרך המועדפת עליו.";
        } else {
            $prompt .= "\n**Important instructions:**\n"
                . "- The token must be in exactly this format, in square brackets\n"
                . "- Add the token at the end of your response\n"
                . "- Use widgets when the customer asks or when it fits the conversation context\n"
                . "- Do not use widgets aggressively or inappropriately\n"
                . "\n"
                . "**⭐ Satisfaction survey rules (survey):**\n"
                . "- Show a satisfaction survey **only** when the customer is satisfied and has no further questions or needs\n"
                . '- Signs the customer is ready for a survey: "thanks", "you helped me", "great", "no more questions", "that\'s all"' . "\n"
                . "- **Never** show a survey in the middle of an active conversation, when the customer is still asking questions, or when there is an unresolved issue\n"
                . "- The survey should only appear at the end of a successful conversation, as a natural closing gesture\n"
                . "\n"
                . "**📇 Automatic lead saving (LEAD_SAVE):**\n"
                . "When you detect that the customer has shared their **name and phone number** during the conversation — save the details using a token:\n"
                . '`[LEAD_SAVE:name=Customer Name|phone=Phone Number|summary=Brief description of the conversation and needs]`' . "\n"
                . "\n"
                . "**Rules:**\n"
                . "- **Required**: name + phone. If only a phone number is provided — ask the customer for their name before saving\n"
                . "- **Optional**: email, summary\n"
                . "- In summary: briefly describe the conversation topic and customer needs (up to 100 words)\n"
                . "- Add the token at the end of your response (it will be automatically removed and not shown to the customer)\n"
                . "- Do not save a lead twice — if you already sent LEAD_SAVE in this conversation, do not send it again\n"
                . "- If the customer also requests a service (meeting, form, etc.) — save the lead **in addition** to the widget\n"
                . "- **Important**: If a form with a phone field is already displayed to the user (attached as a widget in advance with pre-filled values), **ask the user to complete and submit the form** instead of using LEAD_SAVE\n"
                . "\n"
                . "Example:\n"
                . '- Customer: "Hi, I\'m John, my phone is 555-123-4567, I\'m interested in the premium package"' . "\n"
                . '- Response: "Nice to meet you, John! I\'d love to tell you about our premium package... [LEAD_SAVE:name=John|phone=555-123-4567|summary=Customer is interested in the premium package]"' . "\n"
                . "\n"
                . "**🔀 Giving the user a choice:**\n"
                . 'When the customer asks a general question (like "How can I get in touch?") and you have multiple relevant widgets, **let them choose** instead of deciding for them.' . "\n"
                . "Use the token: [WIDGET_PICKER:type1:id1,type2:id2,type3:id3]\n"
                . "\n"
                . "Example:\n"
                . '- Customer: "How can I get in touch?"' . "\n"
                . '- Correct response: "I have several options for you! How would you like to get in touch? [WIDGET_PICKER:form:1,whatsapp:2,calendar_meeting:3]"' . "\n"
                . "\n"
                . "The customer will see attractive choice buttons and can select their preferred method.";
        }

        return $prompt;
    }

    /**
     * Get widgets assigned to a bot.
     *
     * @param object|null $bot Bot data.
     * @return array List of widgets.
     */
    private function get_bot_widgets( $bot = null ): array {
        if ( empty( $bot ) || empty( $bot->id ) ) {
            return array();
        }

        // Get widgets assigned to this bot
        if ( ! class_exists( 'BoostBot_Widget_Model' ) ) {
            return array();
        }

        $widget_model = new BoostBot_Widget_Model();
        $widgets      = $widget_model->get_for_chatbot( $bot->id );

        if ( empty( $widgets ) || ! is_array( $widgets ) ) {
            return array();
        }

        // Filter only enabled widgets (both globally active and per-bot enabled).
        // Note: get_for_chatbot() already filters and returns only fully-configured widgets.
        return array_filter( $widgets, function ( $widget ) {
            return ! empty( $widget->is_active ) && ! empty( $widget->is_enabled );
        } );
    }

    /**
     * Get WooCommerce product card instructions.
     *
     * Tells the AI how to show product cards dynamically.
     *
     * @return string Product card instructions or empty string.
     */
    private function get_woocommerce_product_instructions(): string {
        // Check if WooCommerce is active
        if ( ! class_exists( 'WooCommerce' ) ) {
            return '';
        }

        // Week 27.8: Do NOT dump a large product catalog into the prompt.
        // Keep this list very small and stable; the model can ask clarifying questions.
        $products = wc_get_products( array(
            'limit'   => 10,
            'status'  => 'publish',
            'orderby' => 'title',
            'order'   => 'ASC',
        ) );

        if ( empty( $products ) ) {
            return '';
        }

        $product_list = array();
        $keywords_map = array();
        
        foreach ( $products as $product ) {
            $name  = $product->get_name();
            $id    = $product->get_id();
            $product_list[] = "  - ID {$id}: {$name}";
        }
        $products_str = implode( "\n", $product_list );
        $keywords_str = '';

        $is_he = ( 'he' === $this->language );

        if ( $is_he ) {
            return "### מוצרים מהחנות\n"
                . "PRIORITY: MUST USE when the user asks about products, prices, or shopping.\n"
                . "Always show a product card widget when a product is mentioned or requested.\n"
                . "This is a critical feature — never describe a product without showing its card.\n\n"
                . "### מוצרים מהחנות - קריטי!\n\n"
                . "**⚠️ כשמישהו מבקש לראות/לקנות/לשמוע על מוצר - חובה להשתמש בווידג'ט!**\n\n"
                . "**הפורמט הנכון (תמיד!):**\n"
                . "[WIDGET:product_card:PRODUCT_ID]\n\n"
                . "**רשימת מוצרים בחנות (דוגמה מצומצמת):**\n"
                . "{$products_str}\n"
                . "{$keywords_str}\n\n"
                . "**🔍 מתי להציג כרטיס מוצר (בכל אחד מהמקרים!):**\n"
                . "- לקוח אומר: \"תראה לי...\", \"יש לכם...\", \"מחפש...\", \"אפשר לראות...\"\n"
                . "- לקוח מזכיר מוצר לפי שם: \"אוזניות\", \"Bluetooth\"\n"
                . "- לקוח שואל: \"מה יש לכם?\", \"מה המחיר?\", \"כמה עולה?\"\n"
                . "- לקוח מתעניין: \"יש משהו ב...\", \"מוצר\"\n\n"
                . "**אם אינך בטוח איזה מוצר הכוונה:**\n"
                . "- שאל שאלה קצרה להבהרה (שם/קטגוריה), או בקש תיאור קצר לפני שאתה מציג כרטיס.\n\n"
                . "**✅ דוגמאות תשובות נכונות:**\n"
                . "לקוח: \"תראה לי את האוזניות\"\n"
                . "תשובה: \"בטח! הנה האוזניות שלנו: [WIDGET:product_card:14075]\"\n\n"
                . "לקוח: \"יש לכם אוזניות?\"\n"
                . "תשובה: \"כן! יש לנו אוזניות מעולות: [WIDGET:product_card:14075]\"\n\n"
                . "לקוח: \"מה המחיר של האוזניות?\"\n"
                . "תשובה: \"הנה הפרטים: [WIDGET:product_card:14075]\"\n\n"
                . "**⛔ אסור בשום מצב:**\n"
                . "- לא להשתמש בתבניות {template:...}\n"
                . "- לא לכתוב HTML\n"
                . "- לא לתאר את המוצר במילים בלי הווידג'ט\n"
                . "- תמיד להוסיף את הטוקן [WIDGET:product_card:ID] בסוף התשובה!";
        }

        return "### WooCommerce Products\n"
            . "PRIORITY: MUST USE when the user asks about products, prices, or shopping.\n"
            . "Always show a product card widget when a product is mentioned or requested.\n"
            . "This is a critical feature — never describe a product without showing its card.\n\n"
            . "### Store Products — Critical!\n\n"
            . "**⚠️ When someone asks to see/buy/learn about a product — you MUST use the widget!**\n\n"
            . "**The correct format (always!):**\n"
            . "[WIDGET:product_card:PRODUCT_ID]\n\n"
            . "**Product list in the store (abbreviated sample):**\n"
            . "{$products_str}\n"
            . "{$keywords_str}\n\n"
            . "**🔍 When to show a product card (in ANY of these cases!):**\n"
            . "- Customer says: \"Show me...\", \"Do you have...\", \"I'm looking for...\", \"Can I see...\"\n"
            . "- Customer mentions a product by name: \"headphones\", \"Bluetooth\"\n"
            . "- Customer asks: \"What do you have?\", \"What's the price?\", \"How much does it cost?\"\n"
            . "- Customer is interested: \"Is there something in...\", \"product\"\n\n"
            . "**If you're not sure which product is meant:**\n"
            . "- Ask a short clarifying question (name/category), or request a brief description before showing a card.\n\n"
            . "**✅ Correct response examples:**\n"
            . "Customer: \"Show me the headphones\"\n"
            . "Response: \"Sure! Here are our headphones: [WIDGET:product_card:14075]\"\n\n"
            . "Customer: \"Do you have headphones?\"\n"
            . "Response: \"Yes! We have great headphones: [WIDGET:product_card:14075]\"\n\n"
            . "Customer: \"What's the price of the headphones?\"\n"
            . "Response: \"Here are the details: [WIDGET:product_card:14075]\"\n\n"
            . "**⛔ Never do the following:**\n"
            . "- Do not use {template:...} templates\n"
            . "- Do not write HTML\n"
            . "- Do not describe the product in words without the widget\n"
            . "- Always add the token [WIDGET:product_card:ID] at the end of your response!";
    }

    /**
     * Get page context section.
     *
     * Enhanced with full content, technical specs, and price info.
     *
     * @param array $page_content Page content data.
     * @return string
     */
    private function get_page_context( array $page_content ): string {
        $title        = $page_content['title'] ?? '';
        $summary      = $page_content['summary'] ?? '';
        $url          = $page_content['url'] ?? '';
        $excerpt      = $page_content['excerpt'] ?? '';

        if ( empty( $title ) && empty( $summary ) && empty( $excerpt ) ) {
            return '';
        }

        $is_he = ( 'he' === $this->language );

        $parts = array();
        $parts[] = $is_he ? '### העמוד הנוכחי' : '### Current Page Context';
        $parts[] = 'PRIORITY: Use when the user asks about the page they are currently viewing.';
        $parts[] = 'This provides context about where the visitor is browsing. Reference this';
        $parts[] = 'when the question relates to the current page content.';
        $parts[] = '';
        $parts[] = ( $is_he ? 'כותרת' : 'Title' ) . ": {$title}";
        $parts[] = "URL: {$url}";
        $parts[] = '';

        if ( ! empty( $excerpt ) ) {
            $parts[] = $is_he
                ? 'תמצית העמוד (מבנה קבוע, מקוצץ):'
                : 'Page excerpt (structured, truncated):';
            $parts[] = (string) $excerpt;
        } elseif ( ! empty( $summary ) ) {
            $label = $is_he ? 'סיכום' : 'Summary';
            $parts[] = "{$label}: {$summary}";
        }

        $parts[] = '';
        if ( $is_he ) {
            $parts[] = 'השתמש במידע זה כדי לתת תשובות מדויקות ורלוונטיות על העמוד הנוכחי.';
            $parts[] = 'אם המשתמש שואל על העמוד הנוכחי - השתמש בתמצית לעיל.';
        } else {
            $parts[] = 'Use this information to provide accurate and relevant answers about the current page.';
            $parts[] = 'If the user asks about the current page — use the excerpt above.';
        }

        return implode( "\n", $parts );
    }

    /**
     * Get knowledge context section.
     *
     * @param array|string $knowledge Knowledge items or formatted string.
     * @return string
     */
    private function get_knowledge_context( $knowledge ): string {
        if ( empty( $knowledge ) ) {
            return '';
        }

        $is_he = ( 'he' === $this->language );

        $header_title = $is_he ? '### תוכן רלוונטי מהאתר' : '### Knowledge Base Content';
        $priority_header = "{$header_title}\nPRIORITY: This is content retrieved from the website's knowledge base, relevant to the user's question. Use it as your primary factual source. Never invent information — if it's not in the knowledge base, say so honestly. Include relevant links when available.\n\n";

        $footer = $is_he
            ? "\nהשתמש במידע זה כדי לענות על שאלות. אם מתאים - הפנה לקישור."
            : "\nUse this information to answer questions. When appropriate — include a link.";

        if ( is_string( $knowledge ) ) {
            return $priority_header . $knowledge . $footer;
        }

        $formatted = '';
        $link_label = $is_he ? 'קישור' : 'Link';
        foreach ( $knowledge as $item ) {
            $title   = $item['title'] ?? '';
            $summary = $item['summary'] ?? '';
            $url     = urldecode( (string) ( $item['url'] ?? '' ) );

            $formatted .= "- **{$title}**: {$summary}";
            if ( ! empty( $url ) ) {
                $formatted .= " ({$link_label}: {$url})";
            }
            $formatted .= "\n";
        }

        return $priority_header . $formatted . $footer;
    }

    /**
     * Get response format instructions.
     *
     * @return string
     */
    private function get_response_format(): string {
        if ( 'he' === $this->language ) {
            return "### פורמט תשובה\n"
                . "PRIORITY: Follow these formatting rules to ensure consistent, readable output.\n"
                . "These rules are especially important when presenting prices, packages, or lists.\n\n"
                . "### פורמט תשובה\n"
                . "- תשובות קצרות וממוקדות (2-4 משפטים לשאלות פשוטות)\n"
                . "- שפה טבעית ונעימה\n"
                . "- השתמש באימוג'י במשורה ורק כשמתאים 😊\n\n"
                . "### עיצוב חבילות ומחירים (חשוב מאוד!)\n"
                . "כשמציג חבילות/מחירים/תכניות - השתמש בדיוק בפורמט הזה:\n\n"
                . "שלום! 😊 יש לנו X חבילות:\n\n"
                . "חבילה בסיסית - ₪300\n"
                . "• תכונה ראשונה\n"
                . "• תכונה שנייה\n"
                . "• תכונה שלישית\n\n"
                . "חבילה מתקדמת - ₪600\n"
                . "• תכונה ראשונה\n"
                . "• תכונה שנייה\n"
                . "• תכונה שלישית\n\n"
                . "חבילה פרמיום - ₪1,200\n"
                . "• תכונה ראשונה\n"
                . "• תכונה שנייה\n"
                . "• תכונה שלישית\n\n"
                . "איזו חבילה מתאימה לך?\n\n"
                . "**כללים קריטיים לחבילות:**\n"
                . "1. כל שם חבילה חייב להתחיל ב\"חבילה\" או \"חבילת\" או \"תכנית\" או \"מסלול\"\n"
                . "2. המחיר (₪XXX) חייב להיות באותה שורה עם שם החבילה, מופרד ב-\"-\"\n"
                . "3. כל תכונה בשורה נפרדת שמתחילה ב-\"• \" (bullet + רווח)\n"
                . "4. שורה ריקה בין חבילות\n"
                . "5. סיים בשאלה ממקדת\n\n"
                . "### עיצוב רשימות רגילות\n"
                . "• פריט ראשון\n"
                . "• פריט שני\n"
                . "• פריט שלישי\n\n"
                . "### עיצוב מפרטים טכניים\n"
                . "**מידות:** XXXxYYY ס\"מ\n"
                . "**משקל:** XXX ק\"ג\n"
                . "**חומר:** XXX";
        }

        return "### Response Format\n"
            . "PRIORITY: Follow these formatting rules to ensure consistent, readable output.\n"
            . "These rules are especially important when presenting prices, packages, or lists.\n\n"
            . "### Response Format\n"
            . "- Short and focused responses (2-4 sentences for simple questions)\n"
            . "- Natural and pleasant language\n"
            . "- Use emojis sparingly and only when appropriate 😊\n\n"
            . "### Formatting Packages & Pricing (Very Important!)\n"
            . "When presenting packages/pricing/plans — use exactly this format:\n\n"
            . "Hi! 😊 We have X packages:\n\n"
            . "Basic Package - $300\n"
            . "• First feature\n"
            . "• Second feature\n"
            . "• Third feature\n\n"
            . "Advanced Package - $600\n"
            . "• First feature\n"
            . "• Second feature\n"
            . "• Third feature\n\n"
            . "Premium Package - $1,200\n"
            . "• First feature\n"
            . "• Second feature\n"
            . "• Third feature\n\n"
            . "Which package suits you best?\n\n"
            . "**Critical package rules:**\n"
            . "1. Each package name must start with \"Package\" or \"Plan\" or \"Tier\"\n"
            . "2. The price must be on the same line as the package name, separated by \"-\"\n"
            . "3. Each feature on a separate line starting with \"• \" (bullet + space)\n"
            . "4. Empty line between packages\n"
            . "5. End with a focusing question\n\n"
            . "### Regular List Formatting\n"
            . "• First item\n"
            . "• Second item\n"
            . "• Third item\n\n"
            . "### Technical Specifications Formatting\n"
            . "**Dimensions:** XXXxYYY cm\n"
            . "**Weight:** XXX kg\n"
            . "**Material:** XXX";
    }

    /**
     * Format conversion goals.
     *
     * @param array $goals Goals array.
     * @return string
     */
    private function format_goals( array $goals ): string {
        $is_he = ( 'he' === $this->language );

        $translations = $is_he
            ? array(
                'lead'    => 'לעודד השארת פרטים ליצירת קשר',
                'sale'    => 'לעודד רכישה של מוצרים או שירותים',
                'booking' => 'לעודד קביעת פגישה או תור',
                'support' => 'לספק תמיכה ומענה לשאלות',
            )
            : array(
                'lead'    => 'Encourage leaving contact details',
                'sale'    => 'Encourage purchasing products or services',
                'booking' => 'Encourage booking a meeting or appointment',
                'support' => 'Provide support and answer questions',
            );

        $formatted = '';
        foreach ( $goals as $goal ) {
            $text = $translations[ $goal ] ?? $goal;
            $formatted .= "- {$text}\n";
        }

        return $formatted;
    }

    /**
     * Get site profile from settings.
     *
     * @return array
     */
    private function get_site_profile(): array {
        // Try to get from database
        global $wpdb;
        $table = self::sanitize_identifier( $wpdb->prefix . 'boostbot_site_profile' );

        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
        $profile = $wpdb->get_row(
            "SELECT * FROM `{$table}` ORDER BY id DESC LIMIT 1",
            ARRAY_A
        );
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter

        if ( $profile ) {
            return array(
                'business_name'        => $profile['business_name'] ?? get_bloginfo( 'name' ),
                'business_type'        => $profile['business_type'] ?? '',
                'business_description' => $profile['business_description'] ?? get_bloginfo( 'description' ),
                'main_topics'          => json_decode( $profile['main_topics'] ?? '[]', true ),
                'conversion_goals'     => json_decode( $profile['conversion_goals'] ?? '["lead"]', true ),
            );
        }

        // Fallback to WordPress defaults
        return array(
            'business_name'        => get_bloginfo( 'name' ),
            'business_type'        => '',
            'business_description' => get_bloginfo( 'description' ),
            'main_topics'          => array(),
            'conversion_goals'     => array( 'lead' ),
        );
    }

    /**
     * Get plugin settings.
     *
     * @return array
     */
    private function get_settings(): array {
        return array(
            'bot_name'        => BoostBot_Settings::get( 'bot_name', 'BoostBot' ),
            'tone_of_voice'   => BoostBot_Settings::get( 'tone_of_voice', 'ידידותי ומקצועי' ),
            'widgets_enabled' => BoostBot_Settings::get( 'widgets_enabled', array() ),
        );
    }

    /**
     * Get display name for Claude model.
     *
     * @param string $model_id The model identifier.
     * @return string Human-readable model name.
     */
    private function get_model_display_name( string $model_id ): string {
        $models = array(
            'claude-3-5-sonnet-latest'   => 'Claude 3.5 Sonnet',
            'claude-3-5-sonnet-20241022' => 'Claude 3.5 Sonnet',
            'claude-3-5-haiku-latest'    => 'Claude 3.5 Haiku',
            'claude-3-5-haiku-20241022'  => 'Claude 3.5 Haiku',
            'claude-3-opus-latest'       => 'Claude 3 Opus',
            'claude-3-opus-20240229'     => 'Claude 3 Opus',
            'claude-3-sonnet-20240229'   => 'Claude 3 Sonnet',
            'claude-3-haiku-20240307'    => 'Claude 3 Haiku',
        );

        return $models[ $model_id ] ?? 'Claude';
    }

    /**
     * Get fallback contact instructions when no contact list is available.
     *
     * Provides instructions for the AI on how to handle contact requests
     * when there is no contact list configured for the bot.
     *
     * @since 1.2.0
     * @return string Fallback contact instructions.
     */
    private function get_fallback_contact_instructions(): string {
        $business_name = $this->site_profile['business_name'] ?? get_bloginfo( 'name' );
        $is_he = ( 'he' === $this->language );
        
        $admin_email = get_option( 'admin_email' );
        
        $prompt = $is_he ? "### דרכי יצירת קשר\n" : "### Contact Information\n";
        $prompt .= "PRIORITY: Use when the customer asks to contact the business. No specific contact list is configured for this bot — provide general guidance.\n\n";
        
        if ( $is_he ) {
            $prompt .= "כאשר לקוח מבקש ליצור קשר או לדבר עם נציג:\n";
            $prompt .= "1. הודע לו שאתה יכול לעזור ברוב השאלות\n";
            $prompt .= "2. אם צריך עזרה אישית, הפנה אותו להשאיר פרטים (אם יש טופס זמין)\n";
            $prompt .= "3. אפשר לציין שניתן לפנות דרך האתר\n\n";
        } else {
            $prompt .= "When a customer asks to get in touch or speak with a representative:\n";
            $prompt .= "1. Let them know you can help with most questions\n";
            $prompt .= "2. If they need personal assistance, direct them to leave their details (if a form is available)\n";
            $prompt .= "3. You can mention that they can reach out through the website\n\n";
        }
        
        if ( $admin_email ) {
            $label = $is_he ? 'מייל ליצירת קשר' : 'Contact email';
            $prompt .= "**{$label}:** {$admin_email}\n\n";
        }
        
        if ( $is_he ) {
            $prompt .= "**דוגמה:**\n";
            $prompt .= "לקוח: \"אני רוצה לדבר עם נציג\"\n";
            $prompt .= "תשובה: \"אשמח לעזור לך! אני כאן לכל שאלה. אם תרצה לדבר עם נציג אנושי, ";
            $prompt .= "מומלץ להשאיר פרטים ואחד מהנציגים שלנו יחזור אליך בהקדם. במה אוכל לעזור?\"\n";
        } else {
            $prompt .= "**Example:**\n";
            $prompt .= "Customer: \"I'd like to speak with a representative\"\n";
            $prompt .= "Response: \"I'd be happy to help! I'm here for any question. If you'd like to speak with a human representative, ";
            $prompt .= "I recommend leaving your details and one of our representatives will get back to you shortly. How can I help?\"\n";
        }
        
        return $prompt;
    }

    /**
     * Get template instructions for the AI.
     *
     * Teaches the AI how to use pre-built templates for rich responses.
     * Added in Week 21 as part of the Template System.
     *
     * @since 1.1.0
     * @return string Template instructions for the system prompt.
     */
    private function get_template_instructions(): string {
        if ( ! class_exists( 'BoostBot_Template_Manager' ) ) {
            return '';
        }

        $template_manager = BoostBot_Template_Manager::get_instance();
        $instructions = $template_manager->get_ai_instructions();
        if ( empty( $instructions ) ) {
            return '';
        }

        $is_he = ( 'he' === $this->language );

        $header  = $is_he ? "### תבניות\n" : "### Templates\n";
        $header .= "PRIORITY: Use these pre-built templates when the response matches a template pattern.\n";
        $header .= "Templates provide rich, formatted responses. Only use them when the content genuinely fits the template structure.\n\n";

        return $header . $instructions;
    }

    /**
     * Restrict a SQL identifier to [A-Za-z0-9_] characters.
     *
     * @param string $identifier Raw identifier.
     * @return string Identifier with disallowed characters removed.
     */
    private static function sanitize_identifier( $identifier ) {
        return preg_replace( '/[^A-Za-z0-9_]/', '', (string) $identifier );
    }
}

