<?php
/**
 * AI image search: server-side POST /search for diagnostics; admin “your image” uses the same API from the browser.
 */

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

/**
 * Same readiness rules as text search (registered site + API).
 */
function askagento_ai_image_search_is_available() {
    if (!function_exists('askagento_ai_text_search_is_available') || !askagento_ai_text_search_is_available()) {
        return false;
    }

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

/**
 * Normalize client-provided base64 (optional data URL prefix).
 *
 * @param string $b64 Raw or data-URL base64 fragment.
 */
function askagento_ai_search_strip_image_data_url_prefix($b64) {
    $b64 = (string) $b64;

    if (preg_match('/^data:image\/[^;]+;base64,(.+)$/is', $b64, $m)) {
        return trim($m[1]);
    }

    return trim(str_replace(["\r", "\n", ' '], '', $b64));
}

/**
 * Load wp_get_image_editor and upload helpers during admin-ajax.
 */
function askagento_ai_search_image_load_wp_image_tools() {
    if (function_exists('wp_get_image_editor')) {
        return;
    }

    require_once ABSPATH . 'wp-admin/includes/file.php';
    require_once ABSPATH . 'wp-admin/includes/image.php';
}

/**
 * Resize image data before POST /search (modality image).
 *
 * Always constrains the image to a 500×500 bounding box (aspect preserved; no upscaling)
 * and re-encodes as JPEG when possible. Used for admin upload and “Check status” samples alike.
 *
 * @param string $binary Raw image bytes.
 * @return string Resized bytes, or original on failure / non-image.
 */
function askagento_ai_search_resize_binary_for_search_api($binary) {
    $binary = (string) $binary;

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

    $max_side = (int) apply_filters('askagento_ai_search_api_image_max_side_px', 500);

    if ($max_side < 64) {
        $max_side = 64;
    }

    if ($max_side > 2048) {
        $max_side = 2048;
    }

    $info = @getimagesizefromstring($binary);

    if (!$info || empty($info[0]) || empty($info[1]) || !isset($info[2])) {
        return $binary;
    }

    $src_mime = function_exists('image_type_to_mime_type')
        ? image_type_to_mime_type((int) $info[2])
        : '';

    if ($src_mime === false || $src_mime === '') {
        return $binary;
    }

    $src_mime = (string) $src_mime;

    askagento_ai_search_image_load_wp_image_tools();

    if (!function_exists('wp_tempnam') || !function_exists('wp_get_image_editor')) {
        return $binary;
    }

    $src_path = wp_tempnam('askagento_ai_img_in');

    if (!$src_path) {
        return $binary;
    }

    // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
    if (@file_put_contents($src_path, $binary) === false) {
        wp_delete_file($src_path);

        return $binary;
    }

    $editor = wp_get_image_editor($src_path);

    if (is_wp_error($editor)) {
        wp_delete_file($src_path);

        return $binary;
    }

    /*
     * Maximum width AND height ≤ $max_side (WordPress computes proportional dimensions inside).
     */
    $scaled = $editor->resize($max_side, $max_side, false);

    if (is_wp_error($scaled)) {
        wp_delete_file($src_path);

        return $binary;
    }

    if (method_exists($editor, 'set_quality')) {
        $editor->set_quality((int) apply_filters('askagento_ai_search_image_jpeg_quality', 82));
    }

    $dst_path = wp_tempnam('askagento_ai_img_out');

    if (!$dst_path) {
        wp_delete_file($src_path);

        return $binary;
    }

    $jpeg_mime = 'image/jpeg';
    $saved     = method_exists($editor, 'save') ? $editor->save($dst_path, $jpeg_mime) : new WP_Error('save', 'no save');

    if (is_wp_error($saved)) {
        $saved = $editor->save($dst_path, $src_mime);
    }

    wp_delete_file($src_path);

    if (is_wp_error($saved) || empty($saved['path']) || !is_readable((string) $saved['path'])) {
        wp_delete_file($dst_path);

        return $binary;
    }

    $out_path = (string) $saved['path'];

    // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
    $out = file_get_contents($out_path);

    if ($dst_path !== $out_path) {
        wp_delete_file($dst_path);
    }

    wp_delete_file($out_path);

    return ($out !== false && $out !== '') ? $out : $binary;
}

/**
 * Run Askagento semantic image search (/search with modality image).
 *
 * @param array $args {
 *     @type string      $image_base64 Base64-encoded image bytes (no data URL required).
 *     @type string|null $binary       Raw bytes (alternative to image_base64).
 *     @type int         $limit        Result cap.
 * }
 * @return array{results: array<int,array>, cached?: bool}|WP_Error
 */
function askagento_ai_image_search_execute(array $args) {
    $limit = isset($args['limit']) ? (int) $args['limit'] : (int) apply_filters('askagento_ai_image_search_result_limit', 12);
    $limit = max(1, min($limit, 50));

    $b64 = '';

    if (isset($args['binary']) && is_string($args['binary']) && $args['binary'] !== '') {
        $binary = $args['binary'];
    } elseif (!empty($args['image_base64'])) {
        $b64       = askagento_ai_search_strip_image_data_url_prefix((string) $args['image_base64']);
        $binary    = base64_decode($b64, true);
    } else {
        return new WP_Error('askagento_ai_img_missing', __('No image data was provided.', 'askagento'));
    }

    if (!isset($binary) || !is_string($binary) || $binary === '') {
        return new WP_Error('askagento_ai_img_decode', __('Could not read the image.', 'askagento'));
    }

    /*
     * Hard cap before loading into GD/Imagick (PHP memory). Resized payload is capped separately below.
     */
    $premax = (int) apply_filters(
        'askagento_ai_image_search_premax_resize_bytes',
        32 * 1024 * 1024
    );

    if ($premax > 0 && strlen($binary) > $premax) {
        return new WP_Error(
            'askagento_ai_img_large',
            __('Image file is too large to process.', 'askagento')
        );
    }

    if (strlen($binary) < 32) {
        return new WP_Error('askagento_ai_img_small', __('Image file is empty or damaged.', 'askagento'));
    }

    $binary = askagento_ai_search_resize_binary_for_search_api($binary);

    $max_bytes = (int) apply_filters('askagento_ai_image_search_upload_max_bytes', 5 * 1024 * 1024);

    if ($max_bytes > 0 && strlen($binary) > $max_bytes) {
        return new WP_Error(
            'askagento_ai_img_large',
            __('Image is too large after processing. Try a different file.', 'askagento')
        );
    }

    if (!function_exists('askagento_install_api_is_enabled') || !askagento_install_api_is_enabled()) {
        return new WP_Error(
            'askagento_ai_img_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_img_no_site',
            __('Shop is not registered with Askagento yet.', 'askagento')
        );
    }

    $payload_b64 = base64_encode($binary);

    if ($payload_b64 === false) {
        return new WP_Error('askagento_ai_img_encode', __('Could not prepare image for upload.', 'askagento'));
    }

    $settings = askagento_get_install_settings();

    $body_args = [
        'modality'      => 'image',
        'image_base64'  => $payload_b64,
        'limit'         => $limit,
    ];

    /** @var false|string|null $payload_json */
    $payload_json = wp_json_encode($body_args);

    if ($payload_json === false) {
        return new WP_Error(
            'askagento_ai_img_payload',
            __('Image is too large for this server. Try a smaller photo.', 'askagento')
        );
    }

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

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

    $rq = [
        'method'      => 'POST',
        'timeout'     => 120,
        'redirection' => 0,
        'httpversion' => '1.1',
        'headers'     => [
            'Content-Type'          => 'application/json',
            'X-Askagento-Site-Id'   => $site_id,
            'X-Askagento-Api-Key'   => $api_key,
        ],
        'body'        => $payload_json,
    ];

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

    if (is_wp_error($response)) {
        return new WP_Error(
            'askagento_ai_img_http',
            sprintf(
                /* translators: %s: underlying WordPress HTTP error message */
                __('Search request failed: %s', 'askagento'),
                $response->get_error_message()
            )
        );
    }

    $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_img_remote', askagento_remote_error_to_message($err));
        }

        return new WP_Error(
            'askagento_ai_img_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 || get_post_status($pid) !== 'publish') {
            continue;
        }

        $ptype = get_post_type($pid);

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

        $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'];
        }

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

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

        $out[] = $entry;
    }

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

/**
 * Public URL for the first catalogue product’s main image (for browser-side /search probes).
 *
 * @return string|WP_Error
 */
function askagento_ai_search_first_product_main_image_url($product_id) {
    $product_id = absint($product_id);

    if (!$product_id || !function_exists('wc_get_product')) {
        return new WP_Error('askagento_ai_img_sample', __('Invalid product.', 'askagento'));
    }

    $product = wc_get_product($product_id);

    if (!$product) {
        return new WP_Error('askagento_ai_img_sample', __('Product not found.', 'askagento'));
    }

    $img_id = absint($product->get_image_id());

    if (!$img_id) {
        return new WP_Error(
            'askagento_ai_img_sample',
            __('The first catalogue product has no main image. Add a product image or pick another product.', 'askagento')
        );
    }

    $url = wp_get_attachment_image_url($img_id, 'full');

    if (!is_string($url) || $url === '') {
        return new WP_Error(
            'askagento_ai_img_sample',
            __('Could not resolve the sample product image URL.', 'askagento')
        );
    }

    return $url;
}

/**
 * Build one admin image-search result row (product image only).
 *
 * @param int   $product_id WooCommerce product or variation ID.
 * @param array $from_api Optional: image_url (string), title (string), distance (mixed).
 * @return array<string,mixed>|null
 */
function askagento_ai_search_build_admin_image_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;
    }

    $image_url = '';

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

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

    if ($image_url === '') {
        return null;
    }

    $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));
    }

    $sku  = '';
    $guid = '';

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

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

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

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

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

add_action('wp_ajax_askagento_ai_search_image_admin_check', 'askagento_ajax_ai_search_image_admin_check');

/**
 * Admin: return first-product image URL + metadata; browser POSTs /search (image) like “your image”.
 */
function askagento_ajax_ai_search_image_admin_check() {
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Insufficient permissions.', 'askagento')]);
    }

    check_ajax_referer('askagento_ai_search_image_admin', 'nonce');

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

    $sample_id = askagento_ai_search_get_first_catalog_product_id();

    if (!$sample_id) {
        wp_send_json_error(
            [
                'message' => __('No published WooCommerce products were found.', 'askagento'),
            ]
        );
    }

    $sample_image_url = askagento_ai_search_first_product_main_image_url($sample_id);

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

    $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));

    $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_search_build_admin_image_result_entry(
        $sample_id,
        ['image_url' => $sample_image_url]
    );

    wp_send_json_success(
        [
            '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_item'        => $sample_query_item,
            'sample_image_url'         => $sample_image_url,
            'api_limit'                => $api_limit,
            'visible_max'              => $visible_max,
            'diagnostics_bypass_cache' => true,
        ]
    );
}

/**
 * Decode storefront AJAX `image_base64` (plain or data-URL). Returns binary or WP_Error.
 *
 * @return string|WP_Error
 */
function askagento_ai_image_search_binary_from_ajax_post() {
    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified by caller before this function is invoked.
    $raw = isset($_POST['image_base64']) ? sanitize_text_field(wp_unslash($_POST['image_base64'])) : '';

    if (!is_string($raw) || trim($raw) === '') {
        return new WP_Error('askagento_ai_img_empty', __('No image was uploaded.', 'askagento'));
    }

    $max_chars = (int) apply_filters('askagento_ai_search_image_b64_post_max_chars', 5000000);

    if ($max_chars > 0 && strlen($raw) > $max_chars) {
        return new WP_Error('askagento_ai_img_payload', __('Image is too large.', 'askagento'));
    }

    $b64 = askagento_ai_search_strip_image_data_url_prefix(trim($raw));
    $b64 = preg_replace('/\s+/u', '', $b64);
    $bin = base64_decode($b64, true);

    if (!is_string($bin) || strlen($bin) < 32) {
        return new WP_Error('askagento_ai_img_decode', __('Could not read the image.', 'askagento'));
    }

    return $bin;
}

/**
 * Public AJAX: image search for shortcode / storefront (credentials stay server-side).
 */
function askagento_ajax_ai_image_search_public() {
    if (function_exists('wp_raise_memory_limit')) {
        wp_raise_memory_limit('admin');
    }

    // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
    @set_time_limit(120);

    check_ajax_referer('askagento_ai_image_search', 'nonce');

    if (!askagento_ai_image_search_is_available()) {
        wp_send_json_error(['message' => __('Search is not available.', 'askagento')]);
    }

    $binary = askagento_ai_image_search_binary_from_ajax_post();

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

    $limit = isset($_POST['limit']) ? absint(wp_unslash($_POST['limit'])) : 0;

    if ($limit < 1) {
        $limit = (int) apply_filters('askagento_ai_image_search_result_limit', 12);
    }

    $limit = max(1, min(50, $limit));

    $bundle = askagento_ai_image_search_execute(
        [
            'binary' => $binary,
            'limit'  => $limit,
        ]
    );

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

    wp_send_json_success(
        [
            'results' => isset($bundle['results']) && is_array($bundle['results']) ? $bundle['results'] : [],
        ]
    );
}

add_action('wp_ajax_askagento_ai_image_search', 'askagento_ajax_ai_image_search_public');
add_action('wp_ajax_nopriv_askagento_ai_image_search', 'askagento_ajax_ai_image_search_public');

/**
 * Enrich product IDs returned from a direct browser→API image search with
 * WooCommerce data (permalink, price_html, etc.).
 *
 * Called after the browser posts the image directly to the Askagento API and
 * receives product_ids back.  This request carries no image payload so it is
 * never blocked by WAF/mod_security rules that filter base64 bodies.
 *
 * POST params: nonce, product_ids[] (WooCommerce product IDs)
 */
function askagento_ajax_ai_search_enrich() {
    check_ajax_referer('askagento_ai_image_search', 'nonce');

    if (!function_exists('askagento_ai_text_search_build_shop_result_entry')) {
        wp_send_json_success(['results' => []]);
    }

    $raw_ids = isset($_POST['product_ids']) ? array_map('absint', (array) wp_unslash($_POST['product_ids'])) : [];
    $entries = [];

    foreach ($raw_ids as $pid_raw) {
        $pid = absint($pid_raw);

        if (!$pid) {
            continue;
        }

        if (get_post_status($pid) !== 'publish') {
            continue;
        }

        $ptype = get_post_type($pid);

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

        $entry = askagento_ai_text_search_build_shop_result_entry($pid, []);

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

    wp_send_json_success(['results' => $entries]);
}

add_action('wp_ajax_askagento_ai_search_enrich', 'askagento_ajax_ai_search_enrich');
add_action('wp_ajax_nopriv_askagento_ai_search_enrich', 'askagento_ajax_ai_search_enrich');

/**
 * Enqueue shortcode assets during wp_enqueue_scripts so CSS/JS load in the normal pipeline (not only after the_content).
 */
function askagento_ai_image_search_enqueue_if_post_has_shortcode() {
    if (is_admin()) {
        return;
    }

    if (!function_exists('askagento_ai_image_search_is_available') || !askagento_ai_image_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_image_search')) {
        askagento_enqueue_ai_image_search_standalone_assets();
    }
}

add_action('wp_enqueue_scripts', 'askagento_ai_image_search_enqueue_if_post_has_shortcode', 25);

/**
 * Output image search on WooCommerce shop + product category/tag archives (same places as text search).
 *
 * Shortcodes in the Shop *page* body usually do not run on /shop/ - WooCommerce renders the catalogue template.
 * Disable with: `add_filter('askagento_ai_image_search_on_wc_product_listing', '__return_false');`
 *
 * @return bool
 */
function askagento_ai_image_search_show_on_wc_product_listing() {
    if (!apply_filters('askagento_ai_image_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_image_search_is_available();
}

/**
 * @return void
 */
function askagento_ai_image_search_enqueue_on_wc_product_listing() {
    if (!askagento_ai_image_search_show_on_wc_product_listing()) {
        return;
    }

    askagento_enqueue_ai_image_search_standalone_assets();
}

/**
 * @return void
 */
function askagento_ai_image_search_print_before_shop_loop() {
    if (!askagento_ai_image_search_show_on_wc_product_listing()) {
        return;
    }

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

add_action('wp_enqueue_scripts', 'askagento_ai_image_search_enqueue_on_wc_product_listing', 21);
add_action('woocommerce_before_shop_loop', 'askagento_ai_image_search_print_before_shop_loop', 6);

/**
 * Enqueue assets for standalone image search shortcode.
 */
function askagento_enqueue_ai_image_search_standalone_assets() {
    static $done = false;

    if ($done) {
        return;
    }

    $done = true;

    wp_enqueue_style('dashicons');
    wp_enqueue_style('askagento-front');
    wp_enqueue_script('askagento-front');

    $install  = function_exists('askagento_get_install_settings') ? askagento_get_install_settings() : [];
    $api_url  = isset($install['api_url']) ? rtrim((string) $install['api_url'], '/') : '';
    $site_id  = (string) get_option('askagento_install_site_id', '');
    $api_key  = isset($install['api_key']) ? trim((string) $install['api_key']) : '';
    // Direct browser→API URL: used by front.js to bypass WordPress for the image payload
    // (avoids WAF/mod_security blocks on large base64 POSTs to admin-ajax.php).
    $search_url = ($api_url !== '' && $site_id !== '' && $api_key !== '')
        ? $api_url . '/search'
        : '';

    /** @var array<string, mixed> $cfg */
    $cfg = [
        'ajaxUrl'     => admin_url('admin-ajax.php'),
        'searchUrl'   => $search_url,
        'siteId'      => $site_id,
        'apiKey'      => $api_key,
        'nonce'       => wp_create_nonce('askagento_ai_image_search'),
        'maxRasterPx' => max(64, min(2048, (int) apply_filters('askagento_ai_search_browser_raster_square_px', 260))),
        'resultLimit' => max(1, min(50, (int) apply_filters('askagento_ai_image_search_result_limit', 12))),
    ];

    /** @var false|string $json */
    $json = wp_json_encode($cfg);

    if (is_string($json)) {
        wp_add_inline_script(
            'askagento-front',
            'window.askagentoAiImageSearchFront=' . $json . ';',
            'before'
        );
    }
}

/**
 * Sample query images shown under the image-search bar (tap to run a demo search
 * without uploading a photo). Mirrors the "…or try a sample" block on the v1/v2
 * landing pages.
 *
 * Empty by default: the plugin bundles no sample imagery. Shops opt in with the
 * `askagento_ai_image_search_samples` filter, supplying their own image URLs.
 *
 * @return array<int, array{url:string,label:string}>
 */
function askagento_ai_image_search_samples() {
    /** @var array<int, array{url:string,label:string}> $samples */
    $samples = (array) apply_filters('askagento_ai_image_search_samples', []);

    return $samples;
}

/**
 * Inner HTML for image search.
 *
 * Renders a combined search bar: a text-search input with the camera (photo)
 * button to its right. Both the text and image flows share one status line and
 * one results list. The outer element is the image-search box and the inner
 * element is the text-search box, so the existing front.js bind functions
 * (bindAiImageSearch / bindAiTextSearch) each find what they need and write to
 * the same shared status + results nodes (which carry both class names).
 *
 * @param string $suffix Safe fragment for DOM ids.
 */
function askagento_get_ai_image_search_inner_markup($suffix) {
    $suffix = sanitize_key((string) $suffix);

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

    $fid = esc_attr($suffix);

    $install    = function_exists('askagento_get_install_settings') ? askagento_get_install_settings() : [];
    $api_url    = isset($install['api_url']) ? rtrim((string) $install['api_url'], '/') : '';
    $site_id    = (string) get_option('askagento_install_site_id', '');
    $api_key    = isset($install['api_key']) ? trim((string) $install['api_key']) : '';
    $search_url = ($api_url !== '' && $site_id !== '' && $api_key !== '') ? $api_url . '/search' : '';
    $ajax_url   = admin_url('admin-ajax.php');
    $img_nonce  = wp_create_nonce('askagento_ai_image_search');
    $txt_nonce  = wp_create_nonce('askagento_ai_text_search');
    $max_px     = max(64, min(2048, (int) apply_filters('askagento_ai_search_browser_raster_square_px', 260)));
    $result_lim = max(1, min(50, (int) apply_filters('askagento_ai_image_search_result_limit', 12)));

    $live_enabled    = function_exists('askagento_ai_text_search_live_enabled') ? askagento_ai_text_search_live_enabled() : true;
    $debounce_ms     = function_exists('askagento_ai_text_search_live_debounce_ms') ? askagento_ai_text_search_live_debounce_ms() : 900;
    $client_cache_ms = (int) apply_filters('askagento_ai_text_search_client_cache_ttl_ms', 300000);

    ob_start();

    ?>
    <div
        class="askagento-ai-image-search askagento-ai-image-search--inline"
        data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
        data-nonce="<?php echo esc_attr($img_nonce); ?>"
        data-search-url="<?php echo esc_attr($search_url); ?>"
        data-site-id="<?php echo esc_attr($site_id); ?>"
        data-api-key="<?php echo esc_attr($api_key); ?>"
        data-max-raster-px="<?php echo esc_attr((string) $max_px); ?>"
        data-result-limit="<?php echo esc_attr((string) $result_lim); ?>"
        data-msg-error="<?php echo esc_attr__('Search failed. Try again.', 'askagento'); ?>"
        data-msg-working="<?php echo esc_attr__('Searching…', 'askagento'); ?>"
        data-msg-empty="<?php echo esc_attr__('No matching products.', 'askagento'); ?>"
    >
        <div
            class="askagento-ai-text-search askagento-ai-text-search--inline"
            data-ajax-url="<?php echo esc_attr($ajax_url); ?>"
            data-nonce="<?php echo esc_attr($txt_nonce); ?>"
            data-live-enabled="<?php echo esc_attr($live_enabled ? '1' : '0'); ?>"
            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__('Search products…', 'askagento'); ?>"
                >
                <button type="button" class="askagento-btn askagento-ai-text-search-submit">
                    <?php echo esc_html__('Search', 'askagento'); ?>
                </button>
                <input
                    type="file"
                    id="askagento-ai-is-file-<?php echo esc_attr($fid); ?>"
                    class="screen-reader-text"
                    accept="image/jpeg,image/png,image/gif,image/webp,.jpg,.jpeg,.png,.gif,.webp"
                    capture="environment"
                >
                <button
                    type="button"
                    class="askagento-btn askagento-ai-image-search-camera"
                    aria-label="<?php esc_attr_e('Upload or take a photo to search', 'askagento'); ?>"
                >
                    <span class="dashicons dashicons-camera" aria-hidden="true"></span>
                </button>
            </div>
            <?php $askagento_is_samples = askagento_ai_image_search_samples(); ?>
            <?php if (!empty($askagento_is_samples)) : ?>
                <div
                    class="askagento-ai-image-search-samples"
                    role="group"
                    aria-label="<?php esc_attr_e('Try a sample photo', 'askagento'); ?>"
                >
                    <span class="askagento-ai-image-search-samples-label">
                        <?php echo esc_html__('…or try a sample', 'askagento'); ?>
                    </span>
                    <?php foreach ($askagento_is_samples as $askagento_is_sample) : ?>
                        <?php
                        $sample_url   = isset($askagento_is_sample['url']) ? (string) $askagento_is_sample['url'] : '';
                        $sample_label = isset($askagento_is_sample['label']) ? (string) $askagento_is_sample['label'] : '';

                        if ($sample_url === '') {
                            continue;
                        }
                        ?>
                        <button
                            type="button"
                            class="askagento-ai-image-search-sample"
                            data-sample-url="<?php echo esc_url($sample_url); ?>"
                            aria-label="<?php
                                /* translators: %s: sample product label, e.g. Silver watch. */
                                echo esc_attr(sprintf(__('Search using sample photo: %s', 'askagento'), $sample_label));
                            ?>"
                        >
                            <img
                                src="<?php echo esc_url($sample_url); ?>"
                                alt=""
                                width="56"
                                height="56"
                                loading="lazy"
                                decoding="async"
                            >
                            <?php if ($sample_label !== '') : ?>
                                <span class="askagento-ai-image-search-sample-label"><?php echo esc_html($sample_label); ?></span>
                            <?php endif; ?>
                        </button>
                    <?php endforeach; ?>
                </div>
            <?php endif; ?>
            <div class="askagento-ai-text-search-status askagento-ai-image-search-status" aria-live="polite"></div>
            <ul class="askagento-ai-text-tiles askagento-ai-text-search-results askagento-ai-image-search-results askagento-ai-text-search-results--empty" role="list"></ul>
        </div>
    </div>
    <?php

    return (string) ob_get_clean();
}

/**
 * Wrapped block for frontend JS bootstrap.
 *
 * @param string $suffix Safe fragment for DOM ids.
 */
function askagento_get_ai_image_search_standalone_markup($suffix) {
    ob_start();

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

    return (string) ob_get_clean();
}

/**
 * Shortcode: AI image search (camera + results).
 *
 * Usage: [askagento_image_search] or [askagento_image_search id="homepage"]
 *
 * @param array|string $atts Shortcode attrs.
 */
function askagento_shortcode_ai_image_search($atts) {
    if (!askagento_ai_image_search_is_available()) {
        if (current_user_can('manage_options')) {
            return '<p class="askagento-image-search-admin-hint" style="padding:12px 14px;border:1px solid #c3c4c7;background:#fcf9e8;border-radius:6px;max-width:640px;">'
                . esc_html__(
                    'Askagento image search is hidden until this shop is registered (site ID) and the Discovery API URL is saved. Finish installation under Product Filter → Settings or AI Search Image, then reload this page.',
                    'askagento'
                )
                . '</p>';
        }

        return '';
    }

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

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

add_shortcode('askagento_image_search', 'askagento_shortcode_ai_image_search');
