<?php
/**
 * Site chatbot (OpenAI BYOK + local RAG index).
 *
 * @package Maca_Menu_List
 */

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

require_once MACA_MENULIST_PLUGIN_DIR . 'includes/db-site-chat.php';

/**
 * Whether the site chatbot Pro feature is available.
 *
 * @return bool
 */
function maca_menulist_site_chat_feature_available() {
    return function_exists('maca_menulist_pro_has_feature') && maca_menulist_pro_has_feature('site_chatbot');
}

/**
 * Whether chatbot is enabled in settings.
 *
 * @return bool
 */
function maca_menulist_site_chat_enabled() {
    if (!maca_menulist_site_chat_feature_available()) {
        return false;
    }

    return get_option('maca_menulist_site_chat_enabled', '0') === '1';
}

/**
 * Stored chatbot display name (local language).
 *
 * @return string
 */
function maca_menulist_site_chat_get_name_local() {
    return trim((string) get_option('maca_menulist_site_chat_name', ''));
}

/**
 * Stored chatbot display name (English).
 *
 * @return string
 */
function maca_menulist_site_chat_get_name_en() {
    return trim((string) get_option('maca_menulist_site_chat_name_en', ''));
}

/**
 * Bundled chat avatar presets shipped in assets/.
 *
 * @return array<string, array{filename: string, label: string}>
 */
function maca_menulist_site_chat_get_bundled_avatars() {
    return array(
        'female' => array(
            'filename' => 'ChatAvatar_female.png',
            'label' => __('Female avatar', 'maca-restu-pro'),
        ),
        'male' => array(
            'filename' => 'ChatAvatar_male.png',
            'label' => __('Male avatar', 'maca-restu-pro'),
        ),
    );
}

/**
 * Plugin URL for a bundled chat avatar file.
 *
 * @param string $preset Avatar preset key.
 * @return string
 */
function maca_menulist_site_chat_get_bundled_avatar_plugin_url($preset) {
    $avatars = maca_menulist_site_chat_get_bundled_avatars();

    if (!isset($avatars[ $preset ])) {
        return '';
    }

    $filename = (string) $avatars[ $preset ]['filename'];
    $path = MACA_MENULIST_PLUGIN_DIR . 'assets/' . $filename;

    if (!is_readable($path)) {
        return '';
    }

    return MACA_MENULIST_PLUGIN_URL . 'assets/' . $filename;
}

/**
 * Option key storing a bundled avatar attachment ID.
 *
 * @param string $preset Avatar preset key.
 * @return string
 */
function maca_menulist_site_chat_get_bundled_avatar_attachment_option_key($preset) {
    return 'maca_menulist_site_chat_avatar_attachment_' . sanitize_key((string) $preset);
}

/**
 * Selected chat avatar preset (female, male, custom, or empty).
 *
 * @return string
 */
function maca_menulist_site_chat_get_avatar_preset() {
    $preset = get_option('maca_menulist_site_chat_avatar_preset', '');

    if (!is_string($preset) || $preset === '') {
        return '';
    }

    $preset = sanitize_key($preset);

    if ($preset === 'custom') {
        return 'custom';
    }

    $avatars = maca_menulist_site_chat_get_bundled_avatars();

    return isset($avatars[ $preset ]) ? $preset : '';
}

/**
 * Import one bundled avatar into the media library.
 *
 * @param string $preset Avatar preset key.
 * @return string Public URL or empty string.
 */
function maca_menulist_site_chat_import_bundled_avatar($preset) {
    $avatars = maca_menulist_site_chat_get_bundled_avatars();

    if (!isset($avatars[ $preset ])) {
        return '';
    }

    $option_key = maca_menulist_site_chat_get_bundled_avatar_attachment_option_key($preset);
    $attachment_id = (int) get_option($option_key, 0);

    if ($attachment_id > 0) {
        $existing_url = wp_get_attachment_url($attachment_id);

        if (is_string($existing_url) && $existing_url !== '') {
            return $existing_url;
        }
    }

    $filename = (string) $avatars[ $preset ]['filename'];
    $source_path = MACA_MENULIST_PLUGIN_DIR . 'assets/' . $filename;

    if (!is_readable($source_path)) {
        return maca_menulist_site_chat_get_bundled_avatar_plugin_url($preset);
    }

    if (!function_exists('wp_upload_bits')) {
        require_once ABSPATH . 'wp-admin/includes/file.php';
    }

    if (!function_exists('wp_generate_attachment_metadata')) {
        require_once ABSPATH . 'wp-admin/includes/image.php';
    }

    // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
    $contents = file_get_contents($source_path);

    if ($contents === false) {
        return maca_menulist_site_chat_get_bundled_avatar_plugin_url($preset);
    }

    $upload = wp_upload_bits($filename, null, $contents);

    if (!empty($upload['error'])) {
        return maca_menulist_site_chat_get_bundled_avatar_plugin_url($preset);
    }

    $filetype = wp_check_filetype($filename);
    $attachment = array(
        'post_mime_type' => !empty($filetype['type']) ? (string) $filetype['type'] : 'image/png',
        'post_title' => sanitize_file_name((string) pathinfo($filename, PATHINFO_FILENAME)),
        'post_content' => '',
        'post_status' => 'inherit',
    );

    $attach_id = wp_insert_attachment($attachment, $upload['file']);

    if (is_wp_error($attach_id) || !$attach_id) {
        return maca_menulist_site_chat_get_bundled_avatar_plugin_url($preset);
    }

    $metadata = wp_generate_attachment_metadata((int) $attach_id, $upload['file']);
    wp_update_attachment_metadata((int) $attach_id, $metadata);
    update_option($option_key, (int) $attach_id, false);

    $url = wp_get_attachment_url((int) $attach_id);

    return is_string($url) && $url !== '' ? $url : maca_menulist_site_chat_get_bundled_avatar_plugin_url($preset);
}

/**
 * Import bundled chat avatars into the media library.
 *
 * @return void
 */
function maca_menulist_site_chat_install_bundled_avatars() {
    foreach (array_keys(maca_menulist_site_chat_get_bundled_avatars()) as $preset) {
        maca_menulist_site_chat_import_bundled_avatar($preset);
    }
}

/**
 * Resolve URL for a bundled avatar preset.
 *
 * @param string $preset Avatar preset key.
 * @return string
 */
function maca_menulist_site_chat_get_bundled_avatar_url($preset) {
    $option_key = maca_menulist_site_chat_get_bundled_avatar_attachment_option_key($preset);
    $attachment_id = (int) get_option($option_key, 0);

    if ($attachment_id > 0) {
        $url = wp_get_attachment_url($attachment_id);

        if (is_string($url) && $url !== '') {
            return esc_url($url);
        }
    }

    $plugin_url = maca_menulist_site_chat_get_bundled_avatar_plugin_url($preset);

    return $plugin_url !== '' ? esc_url($plugin_url) : '';
}

/**
 * Avatar image URL for the chatbot.
 *
 * @return string
 */
function maca_menulist_site_chat_get_avatar_url() {
    $preset = maca_menulist_site_chat_get_avatar_preset();

    if ($preset === 'custom') {
        $url = get_option('maca_menulist_site_chat_avatar_url', '');

        return is_string($url) && $url !== '' ? esc_url($url) : '';
    }

    if ($preset !== '') {
        return maca_menulist_site_chat_get_bundled_avatar_url($preset);
    }

    $url = get_option('maca_menulist_site_chat_avatar_url', '');

    if (is_string($url) && $url !== '') {
        return esc_url($url);
    }

    if (maca_menulist_site_chat_enabled()) {
        return maca_menulist_site_chat_get_bundled_avatar_url('female');
    }

    return '';
}

/**
 * Default chatbot title when no custom name is set.
 *
 * @return string
 */
function maca_menulist_site_chat_get_default_display_name() {
    return __('Restaurant assistant', 'maca-restu-pro');
}

/**
 * Chatbot title for guests (bilingual when configured).
 *
 * @return string
 */
function maca_menulist_site_chat_get_display_name() {
    if (!function_exists('maca_menulist_get_bilingual_field')) {
        $local = maca_menulist_site_chat_get_name_local();
        $english = maca_menulist_site_chat_get_name_en();

        if ($local !== '') {
            return $local;
        }

        if ($english !== '') {
            return $english;
        }

        return maca_menulist_site_chat_get_default_display_name();
    }

    $name = maca_menulist_get_bilingual_field(
        maca_menulist_site_chat_get_name_local(),
        maca_menulist_site_chat_get_name_en()
    );

    return $name !== '' ? $name : maca_menulist_site_chat_get_default_display_name();
}

/**
 * Guest-facing UI strings for the chat widget.
 *
 * @return array<string, string>
 */
function maca_menulist_site_chat_get_guest_ui_strings() {
    return array(
        'chatLabel' => __('Chat', 'maca-restu-pro'),
        'placeholder' => __('Ask about the menu, opening hours, events or booking…', 'maca-restu-pro'),
        'send' => __('Send', 'maca-restu-pro'),
        'close' => __('Close', 'maca-restu-pro'),
        'thinking' => __('Thinking…', 'maca-restu-pro'),
        'error' => __('Could not get an answer. Please try again.', 'maca-restu-pro'),
        'yourQuestion' => __('Your question', 'maca-restu-pro'),
    );
}

/**
 * Encrypt chatbot API key for storage.
 *
 * @param string $plain Plain key.
 * @return string
 */
function maca_menulist_site_chat_encrypt_secret($plain) {
    $plain = (string) $plain;

    if ($plain === '') {
        return '';
    }

    if (!function_exists('openssl_encrypt')) {
        return base64_encode($plain);
    }

    $key = hash('sha256', wp_salt('maca_menulist_site_chat'), true);
    $iv = random_bytes(16);
    $encrypted = openssl_encrypt($plain, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);

    if ($encrypted === false) {
        return '';
    }

    return base64_encode($iv . $encrypted);
}

/**
 * Decrypt stored chatbot API key.
 *
 * @param string $stored Stored value.
 * @return string
 */
function maca_menulist_site_chat_decrypt_secret($stored) {
    $stored = (string) $stored;

    if ($stored === '') {
        return '';
    }

    if (!function_exists('openssl_decrypt')) {
        $decoded = base64_decode($stored, true);

        return is_string($decoded) ? $decoded : '';
    }

    $raw = base64_decode($stored, true);

    if ($raw === false || strlen($raw) < 17) {
        return '';
    }

    $iv = substr($raw, 0, 16);
    $encrypted = substr($raw, 16);
    $key = hash('sha256', wp_salt('maca_menulist_site_chat'), true);
    $plain = openssl_decrypt($encrypted, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);

    return is_string($plain) ? $plain : '';
}

/**
 * Stored OpenAI API key (decrypted).
 *
 * @return string
 */
function maca_menulist_site_chat_get_api_key() {
    $stored = get_option('maca_menulist_site_chat_api_key', '');

    if (!is_string($stored) || $stored === '') {
        return '';
    }

    return maca_menulist_site_chat_decrypt_secret($stored);
}

/**
 * Whether an API key is stored.
 *
 * @return bool
 */
function maca_menulist_site_chat_has_api_key() {
    return maca_menulist_site_chat_get_api_key() !== '';
}

/**
 * Embedding model id.
 *
 * @return string
 */
function maca_menulist_site_chat_get_embedding_model() {
    $model = get_option('maca_menulist_site_chat_embedding_model', 'text-embedding-3-small');

    return is_string($model) && $model !== '' ? $model : 'text-embedding-3-small';
}

/**
 * Chat model id.
 *
 * @return string
 */
function maca_menulist_site_chat_get_chat_model() {
    $model = get_option('maca_menulist_site_chat_chat_model', 'gpt-4o-mini');

    return is_string($model) && $model !== '' ? $model : 'gpt-4o-mini';
}

/**
 * Rate limit per hour per IP.
 *
 * @return int
 */
function maca_menulist_site_chat_get_rate_limit() {
    $limit = (int) get_option('maca_menulist_site_chat_rate_limit', 30);

    return max(5, min(200, $limit));
}

/**
 * Selected WordPress page IDs for optional indexing.
 *
 * @return array<int, int>
 */
function maca_menulist_site_chat_get_wp_page_ids() {
    $ids = get_option('maca_menulist_site_chat_wp_page_ids', array());

    if (!is_array($ids)) {
        return array();
    }

    $clean = array();

    foreach ($ids as $id) {
        $id = absint($id);

        if ($id > 0) {
            $clean[] = $id;
        }
    }

    return array_values(array_unique($clean));
}

/**
 * Last index build status.
 *
 * @return array<string, mixed>
 */
function maca_menulist_site_chat_get_index_status() {
    $status = get_option('maca_menulist_site_chat_index_status', array());

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

/**
 * Persist index build status.
 *
 * @param array<string, mixed> $status Status payload.
 * @return void
 */
function maca_menulist_site_chat_set_index_status($status) {
    update_option('maca_menulist_site_chat_index_status', $status, false);
}

/**
 * Call OpenAI API.
 *
 * @param string               $endpoint API path after /v1/ (e.g. embeddings).
 * @param array<string, mixed> $body     JSON body.
 * @return array<string, mixed>|WP_Error
 */
function maca_menulist_site_chat_openai_request($endpoint, $body) {
    $api_key = maca_menulist_site_chat_get_api_key();

    if ($api_key === '') {
        return new WP_Error('maca_site_chat_no_key', __('OpenAI API key is not configured.', 'maca-restu-pro'));
    }

    $endpoint = ltrim((string) $endpoint, '/');
    $url = 'https://api.openai.com/v1/' . $endpoint;

    $response = wp_remote_post(
        $url,
        array(
            'timeout' => 90,
            'headers' => array(
                'Authorization' => 'Bearer ' . $api_key,
                'Content-Type' => 'application/json',
            ),
            'body' => wp_json_encode($body),
        )
    );

    if (is_wp_error($response)) {
        return $response;
    }

    $code = (int) wp_remote_retrieve_response_code($response);
    $raw = wp_remote_retrieve_body($response);
    $data = json_decode($raw, true);

    if ($code < 200 || $code >= 300) {
        $message = __('OpenAI request failed.', 'maca-restu-pro');

        if (is_array($data) && isset($data['error']['message'])) {
            $message = (string) $data['error']['message'];
        }

        return new WP_Error('maca_site_chat_openai_http', $message, array('status' => $code));
    }

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

/**
 * Create embeddings for one or more text inputs.
 *
 * @param array<int, string> $inputs Text inputs.
 * @return array<int, array<int, float>>|WP_Error
 */
function maca_menulist_site_chat_openai_embeddings($inputs) {
    $inputs = array_values(
        array_filter(
            array_map(
                static function ($text) {
                    return trim((string) $text);
                },
                $inputs
            )
        )
    );

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

    $result = maca_menulist_site_chat_openai_request(
        'embeddings',
        array(
            'model' => maca_menulist_site_chat_get_embedding_model(),
            'input' => $inputs,
        )
    );

    if (is_wp_error($result)) {
        return $result;
    }

    if (empty($result['data']) || !is_array($result['data'])) {
        return new WP_Error('maca_site_chat_openai_embed', __('Invalid embedding response from OpenAI.', 'maca-restu-pro'));
    }

    $vectors = array();

    foreach ($result['data'] as $item) {
        if (!isset($item['index'], $item['embedding']) || !is_array($item['embedding'])) {
            continue;
        }

        $vectors[ (int) $item['index'] ] = array_map('floatval', $item['embedding']);
    }

    ksort($vectors);

    return array_values($vectors);
}

/**
 * Chat completion.
 *
 * @param array<int, array<string, string>> $messages Chat messages.
 * @return string|WP_Error
 */
function maca_menulist_site_chat_openai_chat($messages) {
    $result = maca_menulist_site_chat_openai_request(
        'chat/completions',
        array(
            'model' => maca_menulist_site_chat_get_chat_model(),
            'messages' => $messages,
            'temperature' => 0.2,
        )
    );

    if (is_wp_error($result)) {
        return $result;
    }

    $content = isset($result['choices'][0]['message']['content'])
        ? trim((string) $result['choices'][0]['message']['content'])
        : '';

    if ($content === '') {
        return new WP_Error('maca_site_chat_openai_chat', __('Empty response from OpenAI.', 'maca-restu-pro'));
    }

    return $content;
}

/**
 * Cosine similarity between two vectors.
 *
 * @param array<int, float> $a Vector A.
 * @param array<int, float> $b Vector B.
 * @return float
 */
function maca_menulist_site_chat_cosine_similarity($a, $b) {
    if (count($a) !== count($b) || empty($a)) {
        return 0.0;
    }

    $dot = 0.0;
    $norm_a = 0.0;
    $norm_b = 0.0;

    foreach ($a as $i => $value) {
        $value = (float) $value;
        $other = (float) $b[ $i ];
        $dot += $value * $other;
        $norm_a += $value * $value;
        $norm_b += $other * $other;
    }

    if ($norm_a <= 0.0 || $norm_b <= 0.0) {
        return 0.0;
    }

    return $dot / (sqrt($norm_a) * sqrt($norm_b));
}

/**
 * Decode stored embedding JSON.
 *
 * @param string $json JSON string.
 * @return array<int, float>
 */
function maca_menulist_site_chat_decode_embedding($json) {
    $decoded = json_decode((string) $json, true);

    if (!is_array($decoded)) {
        return array();
    }

    return array_map('floatval', $decoded);
}

/**
 * Infer source-type hints from a guest question.
 *
 * @param string $question Guest question.
 * @return array<int, string>
 */
function maca_menulist_site_chat_detect_type_hints($question) {
    $question = trim((string) $question);

    if ($question === '') {
        return array();
    }

    if (function_exists('mb_strtolower')) {
        $question = mb_strtolower($question);
    } else {
        $question = strtolower($question);
    }

    $map = array(
        'hours' => array('öppet', 'öppettid', 'stängt', 'stänger', 'open', 'close', 'hours', 'söndag', 'monday', 'måndag'),
        'event' => array('event', 'evenemang', 'quiz', 'live', 'konsert', 'kalender'),
        'news' => array('nyhet', 'news', 'info'),
        'campaign' => array('kampanj', 'campaign', 'sommarmeny', 'meny'),
        'dish' => array('rätt', 'mat', 'allerg', 'gluten', 'vegan', 'vegetar', 'pris', 'kostar', 'dish', 'menu'),
        'booking' => array('boka', 'bokning', 'bord', 'väntelista', 'book', 'reservation', 'waitlist'),
        'footer' => array('adress', 'telefon', 'mail', 'email', 'hitta', 'contact', 'address', 'phone'),
    );

    $hints = array();

    foreach ($map as $type => $keywords) {
        foreach ($keywords as $keyword) {
            if (strpos($question, $keyword) !== false) {
                $hints[] = $type;
                break;
            }
        }
    }

    return array_values(array_unique($hints));
}

/**
 * Search indexed chunks for a question.
 *
 * @param string               $question  Guest question.
 * @param int                  $limit     Max results.
 * @param array<int, string>   $type_hints Optional source_type filter boost.
 * @return array<int, object>
 */
function maca_menulist_site_chat_search_chunks($question, $limit = 6, $type_hints = array()) {
    $question = trim((string) $question);

    if ($question === '') {
        return array();
    }

    $query_vectors = maca_menulist_site_chat_openai_embeddings(array($question));

    if (is_wp_error($query_vectors) || empty($query_vectors[0])) {
        return array();
    }

    $query_vector = $query_vectors[0];
    $chunks = maca_menulist_db_site_chat_get_all_chunks();
    $scored = array();

    foreach ($chunks as $chunk) {
        $vector = maca_menulist_site_chat_decode_embedding($chunk->embedding);

        if (empty($vector)) {
            continue;
        }

        $score = maca_menulist_site_chat_cosine_similarity($query_vector, $vector);

        if (!empty($type_hints) && in_array((string) $chunk->source_type, $type_hints, true)) {
            $score += 0.08;
        }

        $scored[] = array(
            'score' => $score,
            'chunk' => $chunk,
        );
    }

    usort(
        $scored,
        static function ($a, $b) {
            if ($a['score'] === $b['score']) {
                return 0;
            }

            return ($a['score'] < $b['score']) ? 1 : -1;
        }
    );

    $results = array();

    foreach (array_slice($scored, 0, max(1, (int) $limit)) as $item) {
        if ($item['score'] <= 0.05) {
            continue;
        }

        $results[] = $item['chunk'];
    }

    return $results;
}

/**
 * Build system prompt for the chatbot.
 *
 * @return string
 */
function maca_menulist_site_chat_build_system_prompt() {
    $footer = function_exists('maca_menulist_get_menu_footer_settings')
        ? maca_menulist_get_menu_footer_settings()
        : array();
    $name = !empty($footer['restaurant_name']) ? (string) $footer['restaurant_name'] : get_bloginfo('name');
    $phone = !empty($footer['phone']) ? (string) $footer['phone'] : '';
    $assistant_name = maca_menulist_site_chat_get_display_name();

    $lines = array(
        sprintf(
            /* translators: 1: assistant name, 2: restaurant name */
            __('You are %1$s, the digital assistant for %2$s.', 'maca-restu-pro'),
            $assistant_name,
            $name
        ),
        __('Answer only using the CONTEXT provided by the user message.', 'maca-restu-pro'),
        __('If the answer is not in the context, say you do not know and suggest contacting the restaurant.', 'maca-restu-pro'),
        __('Never invent prices, allergens, opening hours, or events.', 'maca-restu-pro'),
        __('Do not take orders or make table reservations — direct guests to the website booking form or staff.', 'maca-restu-pro'),
        __('Reply in the same language as the guest question (Swedish or English).', 'maca-restu-pro'),
        __('Keep answers concise (2–5 sentences).', 'maca-restu-pro'),
    );

    if ($phone !== '') {
        $lines[] = sprintf(
            /* translators: %s: phone number */
            __('Restaurant phone: %s', 'maca-restu-pro'),
            $phone
        );
    }

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

/**
 * Format retrieved chunks for the LLM context block.
 *
 * @param array<int, object> $chunks Chunk rows.
 * @return string
 */
function maca_menulist_site_chat_format_context($chunks) {
    $parts = array();

    foreach ($chunks as $index => $chunk) {
        $title = isset($chunk->title) ? trim((string) $chunk->title) : '';
        $content = isset($chunk->content) ? trim((string) $chunk->content) : '';
        $type = isset($chunk->source_type) ? (string) $chunk->source_type : '';

        if ($content === '') {
            continue;
        }

        $header = '[' . ($index + 1) . ']';

        if ($type !== '') {
            $header .= ' (' . $type . ')';
        }

        if ($title !== '') {
            $header .= ' ' . $title;
        }

        $parts[] = $header . "\n" . $content;
    }

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

/**
 * Whether the table booking widget was rendered on the current page request.
 *
 * @return bool
 */
function maca_menulist_site_chat_booking_widget_on_page() {
    return !empty($GLOBALS['maca_menulist_booking_widget_rendered']);
}

/**
 * Find the first published page/post whose content matches any SQL LIKE pattern.
 *
 * @param array<int, string> $patterns Content LIKE patterns.
 * @return string Permalink or empty string.
 */
function maca_menulist_site_chat_find_published_page_url(array $patterns) {
    static $cache = array();

    $cache_key = md5(wp_json_encode($patterns));

    if (isset($cache[$cache_key])) {
        return $cache[$cache_key];
    }

    $cache[$cache_key] = '';

    if (empty($patterns)) {
        return $cache[$cache_key];
    }

    global $wpdb;

    foreach ($patterns as $pattern) {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $post_id = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT ID FROM {$wpdb->posts}
                WHERE post_status = 'publish'
                AND post_type IN ('page', 'post')
                AND post_content LIKE %s
                ORDER BY post_type = 'page' DESC, ID ASC
                LIMIT 1",
                $pattern
            )
        );

        if ($post_id > 0) {
            $permalink = get_permalink($post_id);

            if (is_string($permalink) && $permalink !== '') {
                $cache[$cache_key] = $permalink;
                return $cache[$cache_key];
            }
        }
    }

    return $cache[$cache_key];
}

/**
 * Build a page URL with an optional in-page anchor.
 *
 * @param string $page_url  Base permalink.
 * @param string $anchor_id HTML id without leading hash.
 * @return string
 */
function maca_menulist_site_chat_build_page_anchor_url($page_url, $anchor_id = '') {
    $page_url = trim((string) $page_url);
    $anchor_id = ltrim(trim((string) $anchor_id), '#');

    if ($page_url === '') {
        return '';
    }

    if ($anchor_id === '') {
        return $page_url;
    }

    return $page_url . '#' . $anchor_id;
}

/**
 * URL of a published page that contains the online menu shortcode or block.
 *
 * @return string
 */
function maca_menulist_site_chat_find_menu_page_url() {
    static $cached = null;

    if ($cached !== null) {
        return $cached;
    }

    $cached = maca_menulist_site_chat_find_published_page_url(
        array(
            '%[maca_menu_list%',
            '%maca-restu/maca-menu-list%',
            '%maca-restu/menu-list%',
            '%maca-menulist/maca-menu-list%',
            '%maca-menulist/menu-list%',
        )
    );

    return $cached;
}

/**
 * URL of a published page that contains an Info hub block.
 *
 * @param string $section events|news
 * @return string
 */
function maca_menulist_site_chat_find_info_hub_page_url($section = 'events') {
    static $cache = array();

    $section = $section === 'news' ? 'news' : 'events';

    if (isset($cache[$section])) {
        return $cache[$section];
    }

    if ($section === 'news') {
        $patterns = array(
            '%maca-restu/maca-info-news%',
            '%maca-restu/info-news%',
            '%maca-menulist/maca-info-news%',
            '%maca-menulist/info-news%',
        );
    } else {
        $patterns = array(
            '%maca-restu/maca-info-events%',
            '%maca-restu/info-events%',
            '%maca-menulist/maca-info-events%',
            '%maca-menulist/info-events%',
        );
    }

    $cache[$section] = maca_menulist_site_chat_find_published_page_url($patterns);

    return $cache[$section];
}

/**
 * Whether a source URL is useful to show as a guest link.
 *
 * @param string $url Source URL.
 * @return bool
 */
function maca_menulist_site_chat_is_useful_source_url($url) {
    $url = trim((string) $url);

    if ($url === '') {
        return false;
    }

    if (strpos($url, '#') !== false) {
        return true;
    }

    return untrailingslashit($url) !== untrailingslashit(home_url('/'));
}

/**
 * Deduplicated guest-facing links from retrieved sources.
 *
 * @param array<int, array<string, string>> $sources Source rows from chat search.
 * @return array<int, array{url: string, label: string}>
 */
function maca_menulist_site_chat_format_source_links(array $sources) {
    $links = array();
    $seen = array();

    foreach ($sources as $source) {
        if (!is_array($source)) {
            continue;
        }

        $url = isset($source['url']) ? trim((string) $source['url']) : '';

        if (!maca_menulist_site_chat_is_useful_source_url($url) || isset($seen[$url])) {
            continue;
        }

        $seen[$url] = true;
        $title = isset($source['title']) ? trim((string) $source['title']) : '';
        $type = isset($source['type']) ? trim((string) $source['type']) : '';

        if ($title === '') {
            switch ($type) {
                case 'event':
                    $title = __('View event', 'maca-restu-pro');
                    break;
                case 'news':
                    $title = __('Read news', 'maca-restu-pro');
                    break;
                case 'wp_page':
                    $title = __('Open page', 'maca-restu-pro');
                    break;
                case 'dish':
                case 'campaign':
                    $title = __('View menu', 'maca-restu-pro');
                    break;
                case 'booking':
                    $title = __('Book a table', 'maca-restu-pro');
                    break;
                default:
                    $title = __('Read more', 'maca-restu-pro');
            }
        }

        $links[] = array(
            'url' => $url,
            'label' => $title,
        );

        if (count($links) >= 3) {
            break;
        }
    }

    return $links;
}

/**
 * URL of the first published page that contains the booking shortcode or block.
 *
 * @return string
 */
function maca_menulist_site_chat_find_booking_page_url() {
    static $cached = null;

    if ($cached !== null) {
        return $cached;
    }

    $cached = maca_menulist_site_chat_find_published_page_url(
        array(
            '%[maca_table_booking%',
            '%maca-restu/maca-table-booking%',
            '%maca-restu/table-booking%',
            '%maca-menulist/maca-table-booking%',
            '%maca-menulist/table-booking%',
        )
    );

    return $cached;
}

/**
 * Guest-facing booking button config for the chat widget.
 *
 * @return array{enabled: bool, label: string, pageUrl: string, openParam: string, onPage: bool}
 */
function maca_menulist_site_chat_get_booking_cta_config() {
    $enabled = function_exists('maca_menulist_table_booking_enabled') && maca_menulist_table_booking_enabled();
    $label = function_exists('maca_menulist_get_table_booking_button_label')
        ? maca_menulist_get_table_booking_button_label()
        : __('Book a table', 'maca-restu-pro');

    return array(
        'enabled' => $enabled,
        'label' => $label,
        'pageUrl' => maca_menulist_site_chat_find_booking_page_url(),
        'openParam' => 'maca_booking',
        'onPage' => maca_menulist_site_chat_booking_widget_on_page(),
    );
}

/**
 * Whether to show a booking button after the assistant reply.
 *
 * @param string               $question Guest question.
 * @param array<int, object>   $chunks   Retrieved chunks.
 * @param array<int, string>   $hints    Source-type hints.
 * @param array<string, mixed> $args     Optional flags (booking_on_page).
 * @return bool
 */
function maca_menulist_site_chat_should_offer_booking_cta($question, $chunks, $hints, $args = array()) {
    if (!function_exists('maca_menulist_table_booking_enabled') || !maca_menulist_table_booking_enabled()) {
        return false;
    }

    $page_url = maca_menulist_site_chat_find_booking_page_url();
    $on_page = !empty($args['booking_on_page']) || maca_menulist_site_chat_booking_widget_on_page();

    if ($page_url === '' && !$on_page) {
        return false;
    }

    if (in_array('booking', $hints, true)) {
        return true;
    }

    foreach (array_slice($chunks, 0, 3) as $chunk) {
        if (isset($chunk->source_type) && (string) $chunk->source_type === 'booking') {
            return true;
        }
    }

    return false;
}

/**
 * Build booking call-to-action payload for the chat UI.
 *
 * @return array{type: string, label: string, url: string}
 */
function maca_menulist_site_chat_build_booking_cta() {
    return array(
        'type' => 'booking',
        'label' => function_exists('maca_menulist_get_table_booking_button_label')
            ? maca_menulist_get_table_booking_button_label()
            : __('Book a table', 'maca-restu-pro'),
        'url' => maca_menulist_site_chat_find_booking_page_url(),
    );
}

/**
 * Ask the chatbot a question.
 *
 * @param string                              $question Guest question.
 * @param array<int, array<string, string>>   $history  Prior turns (role/content).
 * @param array<string, mixed>                $args     Optional flags (skip_rate_limit, booking_on_page).
 * @return array{answer: string, sources: array<int, array<string, string>>, links: array<int, array{url: string, label: string}>, cta: array<string, string>|null}|WP_Error
 */
function maca_menulist_site_chat_ask($question, $history = array(), $args = array()) {
    $question = trim((string) $question);

    if ($question === '') {
        return new WP_Error('maca_site_chat_empty', __('Please enter a question.', 'maca-restu-pro'));
    }

    if (!maca_menulist_site_chat_enabled()) {
        return new WP_Error('maca_site_chat_disabled', __('The chatbot is not enabled.', 'maca-restu-pro'));
    }

    if (!maca_menulist_site_chat_has_api_key()) {
        return new WP_Error('maca_site_chat_no_key', __('OpenAI API key is not configured.', 'maca-restu-pro'));
    }

    if (maca_menulist_db_site_chat_count_chunks() <= 0) {
        return new WP_Error('maca_site_chat_no_index', __('The chat index has not been built yet.', 'maca-restu-pro'));
    }

    $skip_rate_limit = !empty($args['skip_rate_limit']);

    if (!$skip_rate_limit && !maca_menulist_site_chat_rate_limit_ok()) {
        return new WP_Error('maca_site_chat_rate_limit', __('Too many questions — please try again later.', 'maca-restu-pro'));
    }

    $hints = maca_menulist_site_chat_detect_type_hints($question);
    $chunks = maca_menulist_site_chat_search_chunks($question, 6, $hints);

    if (empty($chunks)) {
        $chunks = maca_menulist_site_chat_search_chunks($question, 6, array());
    }

    $context = maca_menulist_site_chat_format_context($chunks);
    $messages = array(
        array(
            'role' => 'system',
            'content' => maca_menulist_site_chat_build_system_prompt(),
        ),
    );

    if (is_array($history)) {
        foreach (array_slice($history, -4) as $turn) {
            if (!is_array($turn)) {
                continue;
            }

            $role = isset($turn['role']) ? sanitize_key((string) $turn['role']) : '';
            $content = isset($turn['content']) ? trim((string) $turn['content']) : '';

            if (!in_array($role, array('user', 'assistant'), true) || $content === '') {
                continue;
            }

            $messages[] = array(
                'role' => $role,
                'content' => $content,
            );
        }
    }

    $user_message = "CONTEXT:\n" . ($context !== '' ? $context : __('No matching information found in the index.', 'maca-restu-pro'));
    $user_message .= "\n\n" . __('QUESTION:', 'maca-restu-pro') . ' ' . $question;

    $messages[] = array(
        'role' => 'user',
        'content' => $user_message,
    );

    $answer = maca_menulist_site_chat_openai_chat($messages);

    if (is_wp_error($answer)) {
        return $answer;
    }

    if (!$skip_rate_limit) {
        maca_menulist_site_chat_record_rate_limit_hit();
    }

    $sources = array();

    foreach ($chunks as $chunk) {
        $meta = array();

        if (!empty($chunk->meta_json)) {
            $decoded = json_decode((string) $chunk->meta_json, true);
            $meta = is_array($decoded) ? $decoded : array();
        }

        $sources[] = array(
            'type' => isset($chunk->source_type) ? (string) $chunk->source_type : '',
            'title' => isset($chunk->title) ? (string) $chunk->title : '',
            'url' => isset($meta['url']) ? (string) $meta['url'] : '',
        );
    }

    $links = maca_menulist_site_chat_format_source_links($sources);
    $cta = maca_menulist_site_chat_should_offer_booking_cta($question, $chunks, $hints, $args)
        ? maca_menulist_site_chat_build_booking_cta()
        : null;

    if ($cta && !empty($cta['url'])) {
        $booking_url = (string) $cta['url'];
        $links = array_values(
            array_filter(
                $links,
                static function ($link) use ($booking_url) {
                    return !is_array($link) || !isset($link['url']) || (string) $link['url'] !== $booking_url;
                }
            )
        );
    }

    return array(
        'answer' => $answer,
        'sources' => $sources,
        'links' => $links,
        'cta' => $cta,
    );
}

/**
 * Rate-limit transient key for current visitor.
 *
 * @return string
 */
function maca_menulist_site_chat_rate_limit_key() {
    $ip = isset($_SERVER['REMOTE_ADDR']) ? (string) wp_unslash($_SERVER['REMOTE_ADDR']) : 'unknown';

    return 'maca_site_chat_rl_' . md5($ip);
}

/**
 * Whether the visitor is within the hourly rate limit.
 *
 * @return bool
 */
function maca_menulist_site_chat_rate_limit_ok() {
    $key = maca_menulist_site_chat_rate_limit_key();
    $count = (int) get_transient($key);

    return $count < maca_menulist_site_chat_get_rate_limit();
}

/**
 * Record one chat request against the rate limit.
 *
 * @return void
 */
function maca_menulist_site_chat_record_rate_limit_hit() {
    $key = maca_menulist_site_chat_rate_limit_key();
    $count = (int) get_transient($key);
    set_transient($key, $count + 1, HOUR_IN_SECONDS);
}

/**
 * Debounced background reindex (single event).
 *
 * @return void
 */
function maca_menulist_site_chat_schedule_reindex() {
    if (!maca_menulist_site_chat_feature_available() || !maca_menulist_site_chat_has_api_key()) {
        return;
    }

    if (wp_next_scheduled('maca_menulist_site_chat_reindex_event')) {
        return;
    }

    wp_schedule_single_event(time() + 120, 'maca_menulist_site_chat_reindex_event');
}

/**
 * Cron hook name for daily reindex.
 */
function maca_menulist_site_chat_cron_hook() {
    return 'maca_menulist_site_chat_daily_reindex';
}

/**
 * Register daily reindex cron when chatbot is enabled.
 *
 * @return void
 */
function maca_menulist_site_chat_maybe_schedule_cron() {
    $hook = maca_menulist_site_chat_cron_hook();

    if (maca_menulist_site_chat_enabled() && maca_menulist_site_chat_has_api_key()) {
        if (!wp_next_scheduled($hook)) {
            wp_schedule_event(time() + HOUR_IN_SECONDS, 'daily', $hook);
        }
        return;
    }

    $timestamp = wp_next_scheduled($hook);

    if ($timestamp) {
        wp_unschedule_event($timestamp, $hook);
    }
}

add_action('init', 'maca_menulist_site_chat_maybe_schedule_cron');
