<?php
/**
 * AI text search proxy (stores API credentials server-side).
 */

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

add_action('wp_ajax_askagento_ai_text_search', 'askagento_ajax_ai_text_search');
add_action('wp_ajax_nopriv_askagento_ai_text_search', 'askagento_ajax_ai_text_search');

add_shortcode('askagento_text_search', 'askagento_shortcode_ai_text_search');

/**
 * WooCommerce catalogue is registered with the Askagento API and install settings allow calls.
 */
function askagento_ai_text_search_is_available() {
    if (!function_exists('askagento_install_api_is_enabled') || !askagento_install_api_is_enabled()) {
        return false;
    }

    if (trim((string) get_option('askagento_install_site_id', '')) === '') {
        return false;
    }

    $install = function_exists('askagento_get_install_settings') ? askagento_get_install_settings() : [];
    $api_key = isset($install['api_key']) ? trim((string) $install['api_key']) : '';
    if ($api_key === '') {
        return false;
    }

    return !function_exists('askagento_feature_is_enabled') || askagento_feature_is_enabled('ai_search_text');
}

/**
 * Whether storefront / test text search runs automatically while typing.
 */
function askagento_ai_text_search_live_enabled(): bool {
    $stored = get_option('askagento_ai_text_search_live_enabled', true);
    $enabled = ! empty($stored) && ! in_array((string) $stored, ['0', 'false', 'off'], true);

    return (bool) apply_filters('askagento_ai_text_search_live_enabled', $enabled);
}

/**
 * Sanitize “search while typing” toggle (AI Search Text → Setup).
 */
function askagento_sanitize_ai_text_search_live_enabled($value): bool {
    return ! empty($value) && ! in_array((string) $value, ['0', 'false', 'off'], true);
}

/**
 * Live text search debounce (shortcode / storefront), milliseconds.
 * Used by {@see askagento_get_ai_text_search_inner_markup()} and admin AI Search Text “Search with your own text”.
 */
function askagento_ai_text_search_live_debounce_ms(): int {
    $stored = (int) get_option('askagento_ai_text_search_live_debounce_ms', 900);

    if ($stored < 120 || $stored > 2500) {
        $stored = 900;
    }

    $ms = (int) apply_filters('askagento_ai_text_search_live_debounce_ms', $stored);

    if ($ms < 120) {
        $ms = 120;
    }

    if ($ms > 2500) {
        $ms = 2500;
    }

    return $ms;
}

/**
 * Sanitize live-search debounce stored in AI Search Text → Setup (milliseconds).
 */
function askagento_sanitize_ai_text_search_live_debounce_ms($value): int {
    $ms = (int) $value;

    if ($ms < 1) {
        $ms = 900;
    }

    if ($ms < 120) {
        $ms = 120;
    }

    if ($ms > 2500) {
        $ms = 2500;
    }

    return $ms;
}

/**
 * Enqueues assets and passes ajax/nonce when only the search shortcode appears (no product_filter).
 *
 * Uses a distinct localize key so it does not overwrite {@see askagentoFront} when both are present.
 */
function askagento_enqueue_ai_text_search_standalone_assets() {
    static $done = false;

    if ($done) {
        return;
    }

    $done = true;

    wp_enqueue_style('askagento-front');
    wp_enqueue_script('askagento-front');
    wp_localize_script(
        'askagento-front',
        'askagentoAiTextSearchFront',
        [
            'ajaxUrl' => admin_url('admin-ajax.php'),
            'nonce'   => wp_create_nonce('askagento_ai_text_search'),
        ]
    );
}

/**
 * Inner HTML for one AI search field (inside product_filter or standalone root).
 *
 * @param string $suffix Safe fragment for DOM ids (numeric filter id recommended).
 */
function askagento_get_ai_text_search_inner_markup($suffix, $connected = false) {
    $suffix = sanitize_key((string) $suffix);

    if ($suffix === '') {
        $suffix = '0';
    }

    $fid = esc_attr($suffix);

    // Connected mode ([product_filter_search]): the search box drives the sibling
    // product filter's results grid instead of rendering its own tile list.
    $connected = (bool) $connected;

    $live_enabled    = askagento_ai_text_search_live_enabled();
    $debounce_ms     = askagento_ai_text_search_live_debounce_ms();
    $client_cache_ms = (int) apply_filters('askagento_ai_text_search_client_cache_ttl_ms', 300000);

    if ($client_cache_ms < 0) {
        $client_cache_ms = 0;
    }

    $ajax_url = admin_url('admin-ajax.php');
    $nonce    = wp_create_nonce('askagento_ai_text_search');

    ob_start();

    ?>
    <div
        class="askagento-ai-text-search<?php echo esc_attr($connected ? ' askagento-ai-text-search--connected' : ''); ?>"
        <?php if ($connected) : ?>data-askagento-connected="1"<?php endif; ?>
        data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
        data-nonce="<?php echo esc_attr($nonce); ?>"
        data-live-enabled="<?php echo (int) $live_enabled; ?>"
        data-live-debounce-ms="<?php echo esc_attr((string) $debounce_ms); ?>"
        data-live-cache-ms="<?php echo esc_attr((string) $client_cache_ms); ?>"
        data-msg-short="<?php echo esc_attr__('Enter at least 2 characters.', 'askagento'); ?>"
        data-msg-empty="<?php echo esc_attr__('No matching products.', 'askagento'); ?>"
        data-msg-error="<?php echo esc_attr__('Search failed. Try again.', 'askagento'); ?>"
        data-msg-working="<?php echo esc_attr__('Searching…', 'askagento'); ?>"
    >
        <div class="askagento-ai-text-search-row">
            <label class="screen-reader-text" for="askagento-ai-ts-<?php echo esc_attr($fid); ?>">
                <?php echo esc_html__('Search products', 'askagento'); ?>
            </label>
            <input
                type="search"
                id="askagento-ai-ts-<?php echo esc_attr($fid); ?>"
                class="askagento-ai-text-search-input"
                autocomplete="off"
                placeholder="<?php echo esc_attr__('Describe what you are looking for…', 'askagento'); ?>"
            >
            <button type="button" class="askagento-btn askagento-ai-text-search-submit">
                <?php echo esc_html__('Search', 'askagento'); ?>
            </button>
        </div>
        <div class="askagento-ai-text-search-status" aria-live="polite"></div>
        <?php if (!$connected) : ?>
            <ul class="askagento-ai-text-tiles askagento-ai-text-search-results askagento-ai-text-search-results--empty" role="list"></ul>
        <?php endif; ?>
    </div>
    <?php

    return (string) ob_get_clean();
}

/**
 * Wrapped block with data attribute for frontend JS bootstrap when not inside .askagento-filter.
 *
 * @param string $suffix See {@see askagento_get_ai_text_search_inner_markup()}
 */
function askagento_get_ai_text_search_standalone_markup($suffix) {
    ob_start();

    ?>
    <div class="askagento-ai-text-search-root" data-askagento-ai-text-search-root>
        <?php
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- markup builder escapes internally.
        echo askagento_get_ai_text_search_inner_markup($suffix);
        ?>
    </div>
    <?php

    return (string) ob_get_clean();
}

/**
 * Shortcode: AI text search field only - no taxonomy/meta filter UI.
 *
 * Usage (Code block): [askagento_text_search] or [askagento_text_search id="sidebar"]
 *
 * @param array|string $atts Shortcode attrs.
 */
function askagento_shortcode_ai_text_search($atts) {
    if (!askagento_ai_text_search_is_available()) {
        return '';
    }

    $atts = shortcode_atts(
        ['id' => '1'],
        is_array($atts) ? $atts : [],
        'askagento_text_search'
    );
    askagento_enqueue_ai_text_search_standalone_assets();

    return askagento_get_ai_text_search_standalone_markup($atts['id']);
}

add_action('wp_enqueue_scripts', 'askagento_ai_text_search_enqueue_on_wc_product_listing', 20);
add_action('wp_enqueue_scripts', 'askagento_ai_text_search_enqueue_if_post_has_shortcode', 24);
add_action('woocommerce_before_shop_loop', 'askagento_ai_text_search_print_before_shop_loop', 4);

/**
 * Pre-enqueue text search assets during wp_enqueue_scripts for singular pages
 * that contain the [askagento_text_search] shortcode.
 *
 * Without this, assets are only enqueued when the shortcode handler fires during
 * the_content, which can result in askagentoAiTextSearchFront not being available when
 * front.js initialises on DOMContentLoaded.
 */
function askagento_ai_text_search_enqueue_if_post_has_shortcode() {
    if (is_admin()) {
        return;
    }

    if (!askagento_ai_text_search_is_available()) {
        return;
    }

    if (!is_singular()) {
        return;
    }

    $obj = get_queried_object();

    if (!($obj instanceof WP_Post)) {
        return;
    }

    if (has_shortcode((string) $obj->post_content, 'askagento_text_search')) {
        askagento_enqueue_ai_text_search_standalone_assets();
    }
}

/**
 * Output the AI search above the WooCommerce product grid (shop + product category/tag archives).
 *
 * WooCommerce usually does not render the Shop *page* block content on /shop/ - only the catalogue
 * template runs - so a shortcode placed in the Shop page body often never appears.
 *
 * Disable site-wide with:
 * `add_filter('askagento_ai_text_search_on_wc_product_listing', '__return_false');`
 */
function askagento_ai_text_search_show_on_wc_product_listing() {
    if (!apply_filters('askagento_ai_text_search_on_wc_product_listing', true)) {
        return false;
    }

    if (!class_exists('WooCommerce', false)) {
        return false;
    }

    if (!function_exists('is_shop') || !function_exists('is_product_taxonomy')) {
        return false;
    }

    if (!is_shop() && !is_product_taxonomy()) {
        return false;
    }

    return askagento_ai_text_search_is_available();
}

/**
 * @return void
 */
function askagento_ai_text_search_enqueue_on_wc_product_listing() {
    if (!askagento_ai_text_search_show_on_wc_product_listing()) {
        return;
    }

    askagento_enqueue_ai_text_search_standalone_assets();
}

/**
 * @return void
 */
function askagento_ai_text_search_print_before_shop_loop() {
    if (!askagento_ai_text_search_show_on_wc_product_listing()) {
        return;
    }

    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- markup builder escapes internally.
    echo askagento_get_ai_text_search_standalone_markup('wc-shop-loop');
}

/**
 * First words of WooCommerce description/short description for lightweight UI snippets.
 *
 * Falls back from variation to parent description when empty. Default ten words (filter askagento_ai_text_search_description_snippet_word_count, capped 1–50).
 *
 * @param int         $product_id WooCommerce product or variation ID.
 * @param object|null $product    Optional preloaded WC_Product to avoid a redundant wc_get_product() load.
 * @return string Sanitized plaintext (empty when nothing is available).
 */
function askagento_ai_text_search_product_description_snippet($product_id, $product = null) {
    $pid = absint($product_id);

    if (!$pid) {
        return '';
    }

    if (!is_object($product)) {
        if (!function_exists('wc_get_product')) {
            return '';
        }

        $product = wc_get_product($pid);
    }

    if (!$product || !is_object($product) || !method_exists($product, 'get_short_description')) {
        return '';
    }

    /**
     * @param object $p WC_Product-compatible.
     */
    $plaintext = static function ($p) {
        $short = wp_strip_all_tags((string) $p->get_short_description());

        if ($short !== '') {
            return trim($short);
        }

        $full = wp_strip_all_tags((string) $p->get_description());

        return trim($full);
    };

    $text = $plaintext($product);

    if ($text === '' && $product->is_type('variation')) {
        $parent_id = absint($product->get_parent_id());

        if ($parent_id > 0) {
            $parent = wc_get_product($parent_id);

            if ($parent) {
                $text = $plaintext($parent);
            }
        }
    }

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

    $words = preg_split('/\s+/u', $text, -1, PREG_SPLIT_NO_EMPTY);

    if (!$words || !is_array($words)) {
        return '';
    }

    $n = (int) apply_filters('askagento_ai_text_search_description_snippet_word_count', 10);
    $n = max(1, min(50, $n));

    $slice = array_slice($words, 0, $n);
    $out   = implode(' ', $slice);

    if (count($words) > $n) {
        /** @var string $suffix */
        $suffix = (string) apply_filters('askagento_ai_text_search_description_snippet_suffix', '…');

        if ($suffix !== '') {
            $out .= $suffix;
        }
    }

    return sanitize_text_field($out);
}

/**
 * Build one storefront search result row from a product ID and optional API fields.
 *
 * @param int   $product_id WooCommerce product or variation ID.
 * @param array $from_api Optional: title (string), image_url (string), distance (mixed).
 * @return array<string,mixed>|null
 */
function askagento_ai_text_search_build_shop_result_entry($product_id, array $from_api = []) {
    $pid = absint($product_id);

    if (!$pid || get_post_status($pid) !== 'publish') {
        return null;
    }

    $ptype = get_post_type($pid);

    if ($ptype !== 'product' && $ptype !== 'product_variation') {
        return null;
    }

    $thumb = '';

    if (has_post_thumbnail($pid)) {
        $thumb = get_the_post_thumbnail_url($pid, 'woocommerce_thumbnail') ?: '';
    }

    if (!$thumb && !empty($from_api['image_url']) && is_string($from_api['image_url'])) {
        $thumb = esc_url_raw($from_api['image_url']);
    }

    $title = '';

    if (!empty($from_api['title']) && is_string($from_api['title'])) {
        $title = sanitize_text_field($from_api['title']);
    }

    if ($title === '') {
        $title = sanitize_text_field(get_the_title($pid));
    }

    $price_html = '';
    $product    = null;

    if (function_exists('wc_get_product')) {
        $product = wc_get_product($pid);

        if ($product) {
            $price_html = wp_kses_post($product->get_price_html());

            if ($price_html === '' && '' !== (string) $product->get_price()) {
                if (function_exists('wc_get_price_to_display') && function_exists('wc_price')) {
                    $price_html = wp_kses_post(wc_price(wc_get_price_to_display($product)));
                }
            }
        }
    }

    $sku = '';

    if (isset($from_api['sku']) && is_string($from_api['sku'])) {
        $sku = sanitize_text_field($from_api['sku']);
    }

    if ($sku === '' && $product) {
        $sku = sanitize_text_field((string) $product->get_sku());
    }

    $guid = '';

    if ($product !== null && is_object($product) && method_exists($product, 'get_global_unique_id')) {
        $guid = strtolower(trim((string) $product->get_global_unique_id()));
    }

    $description_snippet = askagento_ai_text_search_product_description_snippet($pid, $product);

    $entry = [
        'product_id'           => $pid,
        'sku'                  => $sku,
        'global_unique_id'     => $guid !== '' ? $guid : null,
        'title'                => $title,
        'permalink'            => get_permalink($pid),
        'image_url'            => $thumb,
        'price_html'           => $price_html,
        'distance'             => array_key_exists('distance', $from_api) ? $from_api['distance'] : null,
        'description_snippet' => $description_snippet,
    ];

    return $entry;
}

/**
 * Build one admin text-search result row (image, title, description snippet, price).
 *
 * @param int   $product_id WooCommerce product or variation ID.
 * @param array $from_api Optional: title (string), image_url (string), distance (mixed).
 * @return array<string,mixed>|null
 */
function askagento_ai_text_search_build_admin_text_result_entry($product_id, array $from_api = []) {
    $pid = absint($product_id);

    if (!$pid || get_post_status($pid) !== 'publish') {
        return null;
    }

    $ptype = get_post_type($pid);

    if ($ptype !== 'product' && $ptype !== 'product_variation') {
        return null;
    }

    $thumb = '';

    if (!empty($from_api['image_url']) && is_string($from_api['image_url'])) {
        $thumb = esc_url_raw($from_api['image_url']);
    }

    if ($thumb === '' && has_post_thumbnail($pid)) {
        $thumb = get_the_post_thumbnail_url($pid, 'woocommerce_thumbnail') ?: '';
    }

    $title = '';

    if (!empty($from_api['title']) && is_string($from_api['title'])) {
        $title = sanitize_text_field($from_api['title']);
    }

    if ($title === '') {
        $title = sanitize_text_field(get_the_title($pid));
    }

    $price_html = '';
    $product    = null;

    if (function_exists('wc_get_product')) {
        $product = wc_get_product($pid);

        if ($product) {
            $price_html = wp_kses_post($product->get_price_html());

            if ($price_html === '' && '' !== (string) $product->get_price()) {
                if (function_exists('wc_get_price_to_display') && function_exists('wc_price')) {
                    $price_html = wp_kses_post(wc_price(wc_get_price_to_display($product)));
                }
            }
        }
    }

    $sku = '';

    if (isset($from_api['sku']) && is_string($from_api['sku'])) {
        $sku = sanitize_text_field($from_api['sku']);
    }

    if ($sku === '' && $product) {
        $sku = sanitize_text_field((string) $product->get_sku());
    }

    $guid = '';

    if ($product !== null && is_object($product) && method_exists($product, 'get_global_unique_id')) {
        $guid = strtolower(trim((string) $product->get_global_unique_id()));
    }

    return [
        'product_id'          => $pid,
        'sku'                 => $sku,
        'global_unique_id'    => $guid !== '' ? $guid : null,
        'title'               => $title,
        'image_url'           => $thumb,
        'description_snippet' => askagento_ai_text_search_product_description_snippet($pid, $product),
        'price_html'          => $price_html,
        'distance'            => array_key_exists('distance', $from_api) ? $from_api['distance'] : null,
    ];
}

/**
 * Run Askagento catalogue text search (/search). Used by AJAX and admin diagnostics.
 *
 * @param array $args {
 *     @type string $query Raw search string after sanitize_text_field.
 *     @type bool   $skip_cache Omit transient read/write (admin “Check status” live probe).
 *     @type bool   $admin_text Slim admin payload (image, title, description, price; no permalink).
 *     @type int    $limit     Optional caps (defaults to filter askagento_ai_text_search_result_limit).
 * }
 * @return array{results: array<int,array>, query: string, cached?: bool}|WP_Error
 */
function askagento_ai_text_search_execute(array $args) {
    $q          = sanitize_text_field($args['query'] ?? '');
    $skip_cache = !empty($args['skip_cache']);
    $admin_text = !empty($args['admin_text']);

    $nlen = function_exists('mb_strlen') ? mb_strlen($q, 'UTF-8') : strlen($q);

    if ($nlen < 2) {
        return new WP_Error(
            'askagento_ai_txt_short',
            __('Enter at least 2 characters.', 'askagento')
        );
    }

    $max_chars = (int) apply_filters('askagento_ai_text_search_query_max_chars', 500);

    if ($nlen > $max_chars) {
        return new WP_Error(
            'askagento_ai_txt_long',
            __('Your search text is too long.', 'askagento')
        );
    }

    if (!function_exists('askagento_install_api_is_enabled') || !askagento_install_api_is_enabled()) {
        return new WP_Error(
            'askagento_ai_txt_no_api',
            __('Discovery API is not configured.', 'askagento')
        );
    }

    $site_id = (string) get_option('askagento_install_site_id', '');

    if ($site_id === '') {
        return new WP_Error(
            'askagento_ai_txt_no_site',
            __('Shop is not registered with Askagento yet.', 'askagento')
        );
    }

    $settings = askagento_get_install_settings();
    $api_key  = isset($settings['api_key']) ? trim((string) $settings['api_key']) : '';

    if ($api_key === '') {
        return new WP_Error(
            'askagento_ai_txt_no_api_key',
            __('Discovery API key is missing. Complete installation or check Askagento settings.', 'askagento')
        );
    }

    $limit = isset($args['limit'])
        ? (int) $args['limit']
        : (int) apply_filters('askagento_ai_text_search_result_limit', 12);
    $limit = max(1, min($limit, 50));

    $cache_ttl = (int) apply_filters('askagento_ai_text_search_server_cache_ttl_seconds', 300);

    if ($cache_ttl > 86400) {
        $cache_ttl = 86400;
    }

    $qn = function_exists('mb_strtolower')
        ? mb_strtolower($q, 'UTF-8')
        : strtolower($q);

    $cache_seed = strtolower($site_id) . '|' . $api_key . '|' . $qn . '|' . $limit;
    $transient_key = 'askagento_ai_srch_v3_' . md5($cache_seed);

    if (!$skip_cache && $cache_ttl > 0) {
        $cached_bundle = get_transient($transient_key);

        if (is_array($cached_bundle)
            && isset($cached_bundle['results'])
            && is_array($cached_bundle['results'])) {
            return [
                'results' => $cached_bundle['results'],
                'query'   => $cached_bundle['query'] ?? $q,
                'cached'  => true,
            ];
        }
    }

    $body_args = [
        'query'   => $q,
        'limit'   => $limit,
    ];

    $rq = [
        'method'  => 'POST',
        'timeout' => 45,
        'headers' => [
            'Content-Type'          => 'application/json',
            'X-Askagento-Site-Id'   => $site_id,
            'X-Askagento-Api-Key'   => $api_key,
        ],
        'body'    => wp_json_encode($body_args),
    ];

    $url = rtrim($settings['api_url'], '/') . '/search';
    $response = wp_remote_post($url, $rq);

    if (is_wp_error($response)) {
        return new WP_Error(
            'askagento_ai_txt_http',
            __('Search request failed.', 'askagento')
        );
    }

    $code = wp_remote_retrieve_response_code($response);
    $body_raw = wp_remote_retrieve_body($response);
    $decoded = json_decode($body_raw, true);

    if ($code >= 400 || !is_array($decoded)) {
        $err = '';

        if (is_array($decoded) && isset($decoded['error']) && is_string($decoded['error'])) {
            $err = sanitize_text_field($decoded['error']);
        }

        if ($err !== '') {
            return new WP_Error('askagento_ai_txt_remote', askagento_remote_error_to_message($err));
        }

        return new WP_Error(
            'askagento_ai_txt_bad',
            __('No results returned from search.', 'askagento')
        );
    }

    $rows = isset($decoded['results']) && is_array($decoded['results']) ? $decoded['results'] : [];
    $out = [];

    foreach ($rows as $row) {
        if (!is_array($row)) {
            continue;
        }

        $pid = isset($row['product_id']) ? absint($row['product_id']) : 0;

        if (!$pid) {
            continue;
        }

        // Status/type are validated inside the entry builders (returns null -> skipped below).
        $fragment = [];

        if (!empty($row['title']) && is_string($row['title'])) {
            $fragment['title'] = sanitize_text_field($row['title']);
        }

        if (!empty($row['image_url']) && is_string($row['image_url'])) {
            $fragment['image_url'] = $row['image_url'];
        }

        if (array_key_exists('distance', $row)) {
            $fragment['distance'] = $row['distance'];
        }

        if (!empty($row['sku']) && is_string($row['sku'])) {
            $fragment['sku'] = sanitize_text_field($row['sku']);
        }

        $entry = $admin_text
            ? askagento_ai_text_search_build_admin_text_result_entry($pid, $fragment)
            : askagento_ai_text_search_build_shop_result_entry($pid, $fragment);

        if ($entry === null) {
            continue;
        }

        $out[] = $entry;
    }

    if (!$skip_cache && $cache_ttl > 0) {
        set_transient(
            $transient_key,
            [
                'results' => $out,
                'query'   => $q,
            ],
            $cache_ttl
        );
    }

    return [
        'results' => $out,
        'query'   => $q,
        'cached'  => false,
    ];
}

/**
 * Relevance-ordered product IDs for a text query, used by the connected
 * [product_filter_search] to constrain the filter grid.
 *
 * @param string $query
 * @param int    $limit
 * @return int[]|null Ordered product IDs (possibly empty) on success; null when
 *                    the query is too short, search is unavailable, or the API
 *                    errored — in which case the caller falls back to attribute
 *                    filtering only (rather than blanking the grid).
 */
function askagento_ai_text_search_candidate_ids($query, $limit = null) {
    $query = trim((string) $query);
    $nlen  = function_exists('mb_strlen') ? mb_strlen($query, 'UTF-8') : strlen($query);

    if ($nlen < 2) {
        return null;
    }

    // Keep the connected filter working on the *relevant* text matches only.
    // Semantic search always returns `limit` items; a deep window pulls in a
    // low-relevance tail (e.g. a chain far down a "silver watch" ranking) which
    // an orthogonal filter (price) can then surface. A tighter window keeps the
    // set on-topic. Tunable per shop.
    if ($limit === null) {
        $limit = (int) apply_filters('askagento_connected_search_candidate_limit', 24);
    }
    $limit = max(1, min((int) $limit, 50));

    if (!function_exists('askagento_ai_text_search_is_available') || !askagento_ai_text_search_is_available()) {
        return null;
    }

    $res = askagento_ai_text_search_execute(['query' => $query, 'limit' => (int) $limit]);

    if (is_wp_error($res)) {
        return null;
    }

    $results = isset($res['results']) && is_array($res['results']) ? $res['results'] : [];
    $ids = [];

    foreach ($results as $entry) {
        $pid = is_array($entry) ? (int) ($entry['product_id'] ?? 0) : 0;
        if ($pid > 0) {
            $ids[] = $pid;
        }
    }

    return array_values(array_unique($ids));
}


/**
 * First published WooCommerce catalogue product ID (numeric post order).
 */
function askagento_ai_search_get_first_catalog_product_id() {
    if (function_exists('wc_get_products')) {
        $ids = wc_get_products(
            [
                'status'   => 'publish',
                'limit'    => 1,
                'return'   => 'ids',
                'orderby'  => 'ID',
                'order'    => 'ASC',
                'paginate' => false,
            ]
        );

        if (is_array($ids) && isset($ids[0]) && absint($ids[0]) > 0) {
            return absint($ids[0]);
        }
    }

    $fallback = get_posts(
        [
            'post_type'      => 'product',
            'post_status'    => 'publish',
            'posts_per_page' => 1,
            'orderby'        => 'ID',
            'order'          => 'ASC',
            'fields'         => 'ids',
        ]
    );

    return isset($fallback[0]) ? (int) $fallback[0] : 0;
}

/**
 * Whether $row is the same WooCommerce catalogue entity as the diagnostics sample (id, SKU, GTIN/global id, variation/parent link).
 *
 * Does not collapse sibling variations unless they duplicate-identify via SKU/global id hints.
 *
 * @param array<string,mixed>            $row
 * @param array{sku_norm?: string, guid_norm?: string} $sample_hints Lowercased SKU / GTIN hints from the sample product.
 */
function askagento_ai_search_admin_row_matches_sample_product($sample_id, array $row, array $sample_hints = []) {
    $sample_id    = absint($sample_id);
    $candidate_id = isset($row['product_id']) ? absint($row['product_id']) : 0;

    $sku_hint  = strtolower(trim((string) ($sample_hints['sku_norm'] ?? '')));
    $guid_hint = strtolower(trim((string) ($sample_hints['guid_norm'] ?? '')));

    if (!$sample_id || !$candidate_id) {
        return false;
    }

    if ($sample_id === $candidate_id) {
        return true;
    }

    if ($sku_hint !== '' && isset($row['sku'])) {
        $row_sku = strtolower(trim((string) $row['sku']));

        if ($row_sku !== '' && $row_sku === $sku_hint) {
            return true;
        }
    }

    if ($guid_hint !== '' && isset($row['global_unique_id']) && is_string($row['global_unique_id'])) {
        $row_guid = strtolower(trim($row['global_unique_id']));

        if ($row_guid !== '' && $row_guid === $guid_hint) {
            return true;
        }
    }

    if (!function_exists('wc_get_product')) {
        return false;
    }

    $sample_product = wc_get_product($sample_id);
    $candidate      = wc_get_product($candidate_id);

    if (!$sample_product || !$candidate) {
        return false;
    }

    if ($candidate->is_type('variation')) {
        $parent_id = absint($candidate->get_parent_id());

        if ($parent_id > 0 && $parent_id === $sample_id) {
            return true;
        }
    }

    if ($sample_product->is_type('variation')) {
        $sample_parent = absint($sample_product->get_parent_id());

        if ($sample_parent > 0 && $candidate_id === $sample_parent) {
            return true;
        }
    }

    if (method_exists($sample_product, 'get_global_unique_id')
        && method_exists($candidate, 'get_global_unique_id')) {
        $global_a = strtolower(trim((string) $sample_product->get_global_unique_id()));
        $global_b = strtolower(trim((string) $candidate->get_global_unique_id()));

        if ($global_a !== ''
            && $global_b !== ''
            && $global_a === $global_b) {
            return true;
        }
    }

    return (bool) apply_filters(
        'askagento_ai_search_admin_row_matches_sample_product',
        false,
        $sample_id,
        $candidate_id,
        $row,
        $sample_hints
    );
}

/**
 * Normalize a product title for admin duplicate detection (distinct post ids may still share storefront text).
 */
function askagento_ai_search_normalize_catalog_title_for_diag($title): string {
    $title = (string) $title;
    $title = wp_strip_all_tags($title);
    $title = html_entity_decode($title, ENT_QUOTES | ENT_HTML5, 'UTF-8');
    $title = strtolower(trim($title));

    if (function_exists('preg_replace')) {
        $title = (string) preg_replace('/\s+/u', ' ', $title);
    }

    return trim($title);
}


/**
 * Build search text aligned with install catalogue data (title + descriptions).
 *
 * @param int $product_id WooCommerce product or variation ID.
 */
function askagento_ai_search_sample_query_from_product($product_id) {
    $product_id = absint($product_id);

    if (!$product_id) {
        return '';
    }

    if (!function_exists('wc_get_product')) {
        return sanitize_text_field(get_the_title($product_id));
    }

    $p = wc_get_product($product_id);

    if (!$p) {
        return sanitize_text_field(get_the_title($product_id));
    }

    $parts = array_filter(
        [
            $p->get_name(),
            wp_strip_all_tags($p->get_short_description() ?: ''),
            wp_strip_all_tags($p->get_description() ?: ''),
        ]
    );

    $text = trim(implode("\n", $parts));
    $text = sanitize_text_field($text);

    if ($text === '') {
        $text = sanitize_text_field($p->get_name());
    }

    $cap = (int) apply_filters('askagento_ai_search_admin_sample_query_max_chars', 400);

    if ($cap > 0 && function_exists('mb_strlen') && mb_strlen($text, 'UTF-8') > $cap) {
        $text = mb_substr($text, 0, $cap, 'UTF-8');
    } elseif ($cap > 0 && strlen($text) > $cap) {
        $text = substr($text, 0, $cap);
    }

    return $text;
}


/**
 * AJAX: semantic text search via Askagento API (/search).
 */
function askagento_ajax_ai_text_search() {
    check_ajax_referer('askagento_ai_text_search', 'nonce');

    $q = isset($_POST['q']) ? sanitize_text_field(wp_unslash($_POST['q'])) : '';

    $bundle = askagento_ai_text_search_execute(['query' => $q, 'skip_cache' => false]);

    if (is_wp_error($bundle)) {
        wp_send_json_error(['message' => $bundle->get_error_message()]);
    }

    $payload = [
        'results' => $bundle['results'],
        'query'   => $bundle['query'],
    ];

    if (!empty($bundle['cached'])) {
        $payload['cached'] = true;
    }

    wp_send_json_success($payload);
}


add_action('wp_ajax_askagento_ai_search_admin_check', 'askagento_ajax_ai_search_admin_check');
add_action('wp_ajax_askagento_ai_search_admin_run', 'askagento_ajax_ai_search_admin_run');


/**
 * Resolve admin text-search query, limits, and sample metadata.
 *
 * @param string|null $free_raw Raw free-text query from POST or null for catalogue sample.
 * @return array<string, mixed>|WP_Error
 */
function askagento_ai_search_admin_build_context($free_raw = null) {
    $use_free = is_string($free_raw) && trim($free_raw) !== '';

    $visible_max = max(1, min(12, (int) apply_filters('askagento_ai_search_admin_check_visible_limit', 3)));
    $api_limit   = (int) apply_filters('askagento_ai_search_admin_check_result_limit', $visible_max + 1);
    $api_limit   = max($visible_max + 1, min(50, $api_limit));

    if ($use_free) {
        $sample_query = sanitize_text_field(trim($free_raw));
        $nlen         = function_exists('mb_strlen') ? mb_strlen($sample_query, 'UTF-8') : strlen($sample_query);

        if ($nlen < 2) {
            return new WP_Error(
                'askagento_ai_txt_short',
                __('Enter at least 2 characters.', 'askagento')
            );
        }

        $max_chars = (int) apply_filters('askagento_ai_text_search_query_max_chars', 500);

        if ($nlen > $max_chars) {
            return new WP_Error(
                'askagento_ai_txt_long',
                __('Your search text is too long.', 'askagento')
            );
        }

        return [
            'query_mode'               => 'free_text',
            'sample_product_id'        => null,
            'sample_product_title'     => null,
            'sample_product_sku'       => '',
            'sample_global_unique_id'  => null,
            'sample_title_normalized'  => null,
            'sample_query'             => $sample_query,
            'sample_query_item'        => null,
            'api_limit'                => $api_limit,
            'visible_max'              => $visible_max,
            'diagnostics_bypass_cache' => true,
        ];
    }

    $sample_id = askagento_ai_search_get_first_catalog_product_id();

    if (!$sample_id) {
        return new WP_Error(
            'askagento_ai_no_products',
            __('No published WooCommerce products were found.', 'askagento')
        );
    }

    $sample_query = askagento_ai_search_sample_query_from_product($sample_id);

    if (strlen($sample_query) < 2) {
        return new WP_Error(
            'askagento_ai_no_sample',
            __('Could not build sample text from the first product.', 'askagento')
        );
    }

    $sample_title_norms = [];
    $t_head             = askagento_ai_search_normalize_catalog_title_for_diag((string) get_the_title($sample_id));

    if ($t_head !== '') {
        $sample_title_norms[] = $t_head;
    }

    $sample_sku_out  = '';
    $sample_guid_out = '';

    if (function_exists('wc_get_product')) {
        $qp = wc_get_product($sample_id);

        if ($qp) {
            $sample_sku_out = sanitize_text_field(trim((string) $qp->get_sku()));

            if (method_exists($qp, 'get_global_unique_id')) {
                $sample_guid_out = strtolower(trim((string) $qp->get_global_unique_id()));
            }

            $t_named = askagento_ai_search_normalize_catalog_title_for_diag((string) $qp->get_name());

            if ($t_named !== '' && !in_array($t_named, $sample_title_norms, true)) {
                $sample_title_norms[] = $t_named;
            }
        }
    }

    $sample_query_item = askagento_ai_text_search_build_admin_text_result_entry($sample_id, []);

    return [
        'query_mode'               => 'catalogue_sample',
        'sample_product_id'        => $sample_id,
        'sample_product_title'     => get_the_title($sample_id),
        'sample_product_sku'       => $sample_sku_out,
        'sample_global_unique_id'  => $sample_guid_out !== '' ? $sample_guid_out : null,
        'sample_title_normalized'  => isset($sample_title_norms[0]) ? $sample_title_norms[0] : null,
        'sample_query'             => $sample_query,
        'sample_query_item'        => $sample_query_item,
        'api_limit'                => $api_limit,
        'visible_max'              => $visible_max,
        'diagnostics_bypass_cache' => true,
    ];
}


/**
 * Admin: return first-product sample query + metadata; browser POSTs /search (text) like storefront text search.
 */
function askagento_ajax_ai_search_admin_check() {
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Insufficient permissions.', 'askagento')]);
    }

    check_ajax_referer('askagento_ai_search_admin', 'nonce');

    if (!askagento_ai_text_search_is_available()) {
        wp_send_json_error(
            [
                'message' => __('Complete Askagento installation (site registration) before testing search.', 'askagento'),
            ]
        );
    }

    $free_raw = isset($_POST['free_text']) ? sanitize_text_field(wp_unslash($_POST['free_text'])) : null;
    $context  = askagento_ai_search_admin_build_context($free_raw);

    if (is_wp_error($context)) {
        wp_send_json_error(['message' => $context->get_error_message()]);
    }

    wp_send_json_success($context);
}


/**
 * Admin: run text search server-side (API + WooCommerce enrich in one request).
 */
function askagento_ajax_ai_search_admin_run() {
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Insufficient permissions.', 'askagento')]);
    }

    check_ajax_referer('askagento_ai_search_admin', 'nonce');

    if (!askagento_ai_text_search_is_available()) {
        wp_send_json_error(
            [
                'message' => __('Complete Askagento installation (site registration) before testing search.', 'askagento'),
            ]
        );
    }

    $free_raw = isset($_POST['free_text']) ? sanitize_text_field(wp_unslash($_POST['free_text'])) : null;
    $context  = askagento_ai_search_admin_build_context($free_raw);

    if (is_wp_error($context)) {
        wp_send_json_error(['message' => $context->get_error_message()]);
    }

    $search = askagento_ai_text_search_execute(
        [
            'query'      => $context['sample_query'],
            'skip_cache' => true,
            'admin_text' => true,
            'limit'      => (int) $context['api_limit'],
        ]
    );

    if (is_wp_error($search)) {
        wp_send_json_error(['message' => $search->get_error_message()]);
    }

    $context['results'] = isset($search['results']) && is_array($search['results']) ? $search['results'] : [];

    wp_send_json_success($context);
}

/**
 * Merge WooCommerce shop fields into raw /search API rows for admin diagnostics.
 *
 * @param array<int, array<string, mixed>> $api_rows Raw /search rows from the browser.
 * @return array<int, array<string, mixed>>
 */
function askagento_ai_text_search_enrich_admin_api_rows(array $api_rows) {
    $out = [];

    foreach ($api_rows as $row) {
        if (!is_array($row)) {
            continue;
        }

        $pid = isset($row['product_id']) ? absint($row['product_id']) : 0;

        if (!$pid) {
            continue;
        }

        $fragment = [];

        foreach (['title', 'image_url', 'distance', 'sku'] as $key) {
            if (array_key_exists($key, $row)) {
                $fragment[$key] = $row[$key];
            }
        }

        $entry = askagento_ai_text_search_build_admin_text_result_entry($pid, $fragment);

        if ($entry !== null) {
            $out[] = $entry;
        }
    }

    return $out;
}

add_action('wp_ajax_askagento_ai_search_enrich_admin_rows', 'askagento_ajax_ai_search_enrich_admin_rows');

/**
 * Admin: enrich browser /search rows with WooCommerce price and permalink data.
 */
function askagento_ajax_ai_search_enrich_admin_rows() {
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Insufficient permissions.', 'askagento')]);
    }

    check_ajax_referer('askagento_ai_search_admin', 'nonce');

    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data; sanitized after json_decode via absint/is_array/is_string checks.
    $raw  = isset($_POST['rows']) ? wp_unslash($_POST['rows']) : '';
    $rows = is_string($raw) ? json_decode($raw, true) : null;

    if (!is_array($rows)) {
        wp_send_json_error(['message' => __('Invalid search rows.', 'askagento')]);
    }

    wp_send_json_success(
        [
            'results' => askagento_ai_text_search_enrich_admin_api_rows($rows),
        ]
    );
}
