<?php
/**
 * askagento Installation API
 * 
 * Handles sending product data to the AskAgento API on plugin installation.
 */

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

/**
 * Registered site ID (askagento_install_site_id).
 *
 * @return string
 */
function askagento_get_install_site_id() {
    $site_id = get_option('askagento_install_site_id', '');

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

/**
 * Get installation API settings.
 *
 * The API URL is hardcoded - wp_parse_args only fills missing keys, so a stored
 * empty string from a removed admin form would otherwise shadow the default.
 */
function askagento_get_install_settings() {
    $settings = wp_parse_args(get_option('askagento_install_settings', []), [
        'api_url'       => ASKAGENTO_API_BASE,
        'api_key'       => '',
        'last_install'  => null,
        'last_status'   => null,
    ]);

    // API URL is hardcoded; force the canonical versioned base so installs that
    // stored an older value (e.g. the unversioned root) are migrated transparently.
    $settings['api_url'] = ASKAGENTO_API_BASE;

    return $settings;
}

/**
 * Check if installation API is enabled (API URL is hardcoded, so always true).
 */
function askagento_install_api_is_enabled() {
    return true;
}

/**
 * Best-effort IPv4 for the shop host (DNS resolution of site_url hostname).
 *
 * May be a CDN edge IP, not the origin server.
 *
 * @return array{shop_ip?: string}
 */
function askagento_collect_shop_ip_metadata() {
    $host = wp_parse_url(site_url(), PHP_URL_HOST);
    $out = [];

    if (is_string($host) && $host !== '') {
        $resolved = gethostbyname($host);
        if (
            is_string($resolved)
            && $resolved !== $host
            && filter_var($resolved, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)
        ) {
            $out['shop_ip'] = $resolved;
        }
    }

    return $out;
}


/**
 * Send product data to the AskAgento API on plugin installation
 * 
 * @param bool $show_notice Whether to show admin notices (default: true)
 * @return array|WP_Error Result with 'status' => 'sent' or 'error'
 */
function askagento_send_install_data($show_notice = true) {
    // Silently skip if API is not configured - user hasn't set it up yet
    if (!askagento_install_api_is_enabled()) {
        return new WP_Error('not_enabled', __('Installation API not configured', 'askagento'));
    }
    
    $settings = askagento_get_install_settings();
    $products = askagento_get_all_products_for_install();
    
    if (empty($products)) {
        if ($show_notice) {
            update_option('askagento_install_admin_notice', [
                'type'  => 'warning',
                'title' => __('No Products', 'askagento'),
                'text'  => __('No products found to send to the server.', 'askagento'),
                'time'  => current_time('mysql'),
            ], false);
        }
        return new WP_Error('no_products', __('No products to send', 'askagento'));
    }
    
    $domain = wp_parse_url(site_url(), PHP_URL_HOST);

    $payload = [
        'domain'              => $domain,
        'plugin_version'      => ASKAGENTO_VERSION,
        'woocommerce_version' => defined('WC_VERSION') ? WC_VERSION : 'unknown',
        'products'            => $products,
        'total_products'      => count($products),
        'system'              => 'woocommerce',
    ];

    $ip_meta = askagento_collect_shop_ip_metadata();
    if (!empty($ip_meta['shop_ip'])) {
        $payload['shop_ip'] = $ip_meta['shop_ip'];
    }

    $args = [
        'method'      => 'POST',
        'timeout'     => 60,
        'redirection' => 0,
        'httpversion' => '1.1',
        'blocking'    => true,
        'headers'     => [
            'Content-Type'  => 'application/json',
        ],
        'body'        => wp_json_encode($payload),
    ];

    $response = wp_remote_post($settings['api_url'] . '/install', $args);
    
    if (is_wp_error($response)) {
        if ($show_notice) {
            update_option('askagento_install_admin_notice', [
                'type'  => 'error',
                'title' => __('Installation Failed', 'askagento'),
                'text'  => __('Could not connect to the server: ', 'askagento') . $response->get_error_message(),
                'time'  => current_time('mysql'),
            ], false);
        }
        return $response;
    }
    
    $response_code = wp_remote_retrieve_response_code($response);
    $response_body = json_decode(wp_remote_retrieve_body($response), true);

    // Surface server-side rejections (e.g. catalogue too large → contact sales) to the
    // merchant instead of the generic "installation started" notice.
    if ($response_code < 200 || $response_code >= 300) {
        $server_msg = (is_array($response_body) && !empty($response_body['error']))
            ? (string) $response_body['error']
            : sprintf(/* translators: %d: HTTP status code */ __('Installation failed (HTTP %d).', 'askagento'), $response_code);
        update_option('askagento_install_last_send', current_time('mysql'), true);
        update_option('askagento_install_last_status', 'error', true);
        if ($show_notice) {
            update_option('askagento_install_admin_notice', [
                'type'  => 'error',
                'title' => __('Installation failed', 'askagento'),
                'text'  => $server_msg,
                'time'  => current_time('mysql'),
            ], false);
        }
        return new WP_Error('install_failed', $server_msg, ['status' => $response_code]);
    }

    $site_id = is_array($response_body) ? sanitize_text_field($response_body['site_id'] ?? '') : '';
    if (!empty($site_id)) {
        update_option('askagento_install_site_id', $site_id, true);
        askagento_clear_features_cache($site_id);
    }

    $api_key_from_server = is_array($response_body)
        ? sanitize_text_field($response_body['api_key'] ?? '')
        : '';
    if ($api_key_from_server !== '' && $response_code >= 200 && $response_code < 300) {
        $stored_install = get_option('askagento_install_settings', []);
        if (! is_array($stored_install)) {
            $stored_install = [];
        }
        $stored_install['api_key'] = $api_key_from_server;
        update_option('askagento_install_settings', $stored_install, true);
    }

    update_option('askagento_install_last_send', current_time('mysql'), true);
    update_option('askagento_install_last_status', $response_code >= 200 && $response_code < 300 ? 'processing' : 'error', true);
    if ($response_code >= 200 && $response_code < 300) {
        askagento_reset_install_processing_notice_dismissed();
        update_option('askagento_install_last_product_count', count($products), true);
        delete_option('askagento_install_completion_notice_pending');
    }

    if ($response_code >= 200 && $response_code < 300) {
        askagento_recommend_upsell_reset_activity_options();
        if (function_exists('askagento_catalogue_sync_record_success')) {
            askagento_catalogue_sync_record_success(null, ['trigger' => 'install', 'products' => count($products)]);
        }
        if (function_exists('askagento_catalogue_sync_schedule_cron')) {
            askagento_catalogue_sync_schedule_cron();
        }
    }

    if ($show_notice) {
        update_option('askagento_install_admin_notice', [
            'type'  => 'info',
            'title' => __('Installation started', 'askagento'),
            /* translators: %d is the number of products queued for processing */
            'text'  => sprintf(__('Installation request sent. %d products queued for processing.', 'askagento'), count($products)),
            'time'  => current_time('mysql'),
        ], false);
    }
    
    return [
        'status' => 'sent',
        'installation_id' => $site_id,
        'products_received' => count($products),
    ];
}

/**
 * Notify API on plugin uninstall so remote data can be removed
 *
 * @return array|WP_Error
 */
function askagento_send_uninstall_data() {
    $settings = askagento_get_install_settings();
    $site_id = function_exists('askagento_get_install_site_id')
        ? askagento_get_install_site_id()
        : get_option('askagento_install_site_id', '');

    if (empty($settings['api_url'])) {
        return new WP_Error('not_configured', __('Installation API not configured', 'askagento'));
    }

    if (empty($site_id)) {
        return new WP_Error('missing_site_id', __('No stored site ID found for uninstall', 'askagento'));
    }

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

    if ($api_key === '') {
        return new WP_Error('missing_api_key', __('Discovery API key is missing; cannot call uninstall.', 'askagento'));
    }

    $domain = wp_parse_url(site_url(), PHP_URL_HOST);

    $payload = [
        'domain'              => $domain,
        'plugin_version'      => ASKAGENTO_VERSION,
        'woocommerce_version' => defined('WC_VERSION') ? WC_VERSION : 'unknown',
        'system'              => 'woocommerce',
    ];

    $args = [
        'method'      => 'POST',
        'timeout'     => 10,
        'redirection' => 5,
        'httpversion' => '1.1',
        'blocking'    => true,
        'headers'     => [
            'Content-Type'          => 'application/json',
            'X-Askagento-Site-Id'   => $site_id,
            'X-Askagento-Api-Key'   => $api_key,
        ],
        'body'        => wp_json_encode($payload),
    ];

    $response = wp_remote_post($settings['api_url'] . '/uninstall', $args);
    if (is_wp_error($response)) {
        return $response;
    }

    $code = wp_remote_retrieve_response_code($response);
    if ($code >= 200 && $code < 300) {
        return json_decode(wp_remote_retrieve_body($response), true);
    }

    return new WP_Error('api_error', __('Uninstall API returned error', 'askagento') . ': ' . $code);
}

/**
 * Product attributes for discovery / Auto Label catalogue: non-empty values only.
 *
 * Taxonomy attributes (pa_*) are exported as term names for IDs that still exist.
 * Orphan term IDs or empty selections are omitted so cloud Analyze matches the admin UI.
 *
 * @param \WC_Product $product WooCommerce product.
 * @return array<string, array<int, string>>
 */
function askagento_install_collect_product_attributes_for_api($product) {
    $out = [];
    if (!$product instanceof \WC_Product) {
        return $out;
    }

    $raw_attrs = $product->get_attributes();
    if (empty($raw_attrs) || !is_array($raw_attrs)) {
        return $out;
    }

    foreach ($raw_attrs as $attr_name => $attr_value) {
        if (!($attr_value instanceof \WC_Product_Attribute)) {
            continue;
        }
        $opts = $attr_value->get_options();
        if ($attr_value->is_taxonomy()) {
            $names = [];
            foreach ((array) $opts as $tid) {
                $tid = (int) $tid;
                if ($tid <= 0) {
                    continue;
                }
                $term = get_term($tid);
                if ($term instanceof \WP_Term && !is_wp_error($term)) {
                    $n = trim(wp_strip_all_tags((string) $term->name));
                    if ($n !== '') {
                        $names[] = $n;
                    }
                }
            }
            $names = array_values(array_unique($names));
            if (!empty($names)) {
                $out[(string) $attr_name] = $names;
            }
            continue;
        }
        $strings = [];
        foreach ((array) $opts as $o) {
            $s = is_string($o) ? trim($o) : trim((string) $o);
            if ($s !== '') {
                $strings[] = $s;
            }
        }
        $strings = array_values(array_unique($strings));
        if (!empty($strings)) {
            $out[(string) $attr_name] = $strings;
        }
    }

    return $out;
}

/**
 * Variation attribute values keyed by human-readable label (e.g. "Color" => "Red").
 *
 * Taxonomy attribute values are resolved from term slug to term name so they
 * match what shoppers see; custom attribute values are passed through.
 *
 * @param \WC_Product_Variation $variation
 * @return array<string, string>
 */
function askagento_install_collect_variation_attributes_for_api($variation) {
    $out = [];
    if (!$variation instanceof \WC_Product_Variation) {
        return $out;
    }

    foreach ($variation->get_attributes() as $attr_key => $value) {
        $value = is_string($value) ? trim($value) : trim((string) $value);
        if ($value === '') {
            continue; // "Any <attribute>" - not a concrete selection.
        }
        $label = wc_attribute_label($attr_key, $variation);
        $display_value = $value;
        if (taxonomy_exists($attr_key)) {
            $term = get_term_by('slug', $value, $attr_key);
            if ($term instanceof \WP_Term && !is_wp_error($term)) {
                $display_value = $term->name;
            }
        }
        $out[(string) $label] = $display_value;
    }

    return $out;
}

/**
 * Variations for a variable product, formatted for the install/sync API.
 *
 * Each entry carries id, sku, price, attributes (label => value) and a distinct
 * variation image_url (null when the variation has no own image, so the cloud
 * side falls back to the parent image and avoids a duplicate vector).
 *
 * @param \WC_Product $product
 * @return array<int, array<string, mixed>>
 */
function askagento_install_collect_product_variations_for_api($product) {
    $out = [];
    if (!$product instanceof \WC_Product || !$product->is_type('variable')) {
        return $out;
    }

    foreach ($product->get_children() as $variation_id) {
        $variation = wc_get_product($variation_id);
        if (!$variation instanceof \WC_Product_Variation) {
            continue;
        }
        if ($variation->get_status() !== 'publish') {
            continue;
        }

        // 'edit' context: own image only (no parent fallback), so we can detect
        // whether the variation has a distinct image worth its own vector.
        $image_url = null;
        $image_id = $variation->get_image_id('edit');
        if ($image_id) {
            $url = wp_get_attachment_url($image_id);
            if (!empty($url)) {
                $image_url = $url;
            }
        }

        $out[] = [
            'id'         => $variation->get_id(),
            'sku'        => $variation->get_sku(),
            'price'      => $variation->get_price(),
            'attributes' => askagento_install_collect_variation_attributes_for_api($variation),
            'image_url'  => $image_url,
        ];
    }

    return $out;
}

/**
 * Get all products formatted for installation API
 */
function askagento_get_all_products_for_install() {
    global $wpdb;
    
    $products = [];
    
    // Get all published products
    $query = new WP_Query([
        'post_type'      => 'product',
        'post_status'    => 'publish',
        'posts_per_page' => -1,
        'fields'         => 'ids',
    ]);
    
    foreach ($query->posts as $product_id) {
        $product = wc_get_product($product_id);
        
        if (!$product) {
            continue;
        }
        
        // Get image IDs
        $image_ids = [];
        $main_image_id = $product->get_image_id();
        if ($main_image_id) {
            $image_ids[] = $main_image_id;
        }
        $gallery_image_ids = $product->get_gallery_image_ids();
        if (!empty($gallery_image_ids)) {
            $image_ids = array_merge($image_ids, $gallery_image_ids);
        }

        // Keep only the first valid image URL (main image first, then gallery).
        $first_image_url = null;
        foreach ($image_ids as $image_id) {
            $url = wp_get_attachment_url($image_id);
            if (!empty($url)) {
                $first_image_url = $url;
                break;
            }
        }

        // Get categories
        $categories = [];
        $terms = get_the_terms($product_id, 'product_cat');
        if ($terms && !is_wp_error($terms)) {
            $categories = array_values(array_map(function($t) {
                return $t->name;
            }, $terms));
        }

        // Get tags
        $tags = [];
        $terms = get_the_terms($product_id, 'product_tag');
        if ($terms && !is_wp_error($terms)) {
            $tags = array_values(array_map(function($t) {
                return $t->name;
            }, $terms));
        }

        $attributes = askagento_install_collect_product_attributes_for_api($product);
        $variations = askagento_install_collect_product_variations_for_api($product);

        $product_data = [
            'id'                => $product->get_id(),
            'title'             => $product->get_name(),
            'name'              => $product->get_name(),
            'description'       => $product->get_description(),
            'short_description' => $product->get_short_description(),
            'sku'               => $product->get_sku(),
            'price'             => $product->get_price(),
            'regular_price'     => $product->get_regular_price(),
            'sale_price'        => $product->get_sale_price(),
            'stock_quantity'    => $product->get_stock_quantity(),
            'status'            => $product->get_status(),
            'type'              => $product->get_type(),
            'image_url'         => $first_image_url,
            'categories'        => $categories,
            'tags'              => $tags,
            'attributes'        => $attributes,
            'variations'        => $variations,
            'created_at'        => $product->get_date_created()
                                   ? $product->get_date_created()->date('c')
                                   : null,
            'updated_at'        => $product->get_date_modified()
                                   ? $product->get_date_modified()->date('c')
                                   : null,
        ];

        $products[] = $product_data;
    }
    
    return $products;
}

/**
 * Check installation status via API.
 *
 * @param string $site_id Installation site identifier.
 * @return array|WP_Error
 */
function askagento_check_install_status($site_id) {
    $settings = askagento_get_install_settings();

    if (!askagento_install_api_is_enabled()) {
        return new WP_Error('not_enabled', __('Installation API not enabled', 'askagento'));
    }

    if (empty($site_id)) {
        return new WP_Error('missing_installation_id', __('No installation ID found', 'askagento'));
    }

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

    if ($api_key === '') {
        return new WP_Error('missing_api_key', __('Discovery API key is missing; cannot check install status.', 'askagento'));
    }

    $args = [
        'method'  => 'GET',
        'timeout' => 15,
        'headers' => [
            'Content-Type'          => 'application/json',
            'X-Askagento-Site-Id'   => $site_id,
            'X-Askagento-Api-Key'   => $api_key,
        ],
    ];

    $response = wp_remote_get($settings['api_url'] . '/install', $args);
    if (is_wp_error($response)) {
        return $response;
    }

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

    if ($code >= 200 && $code < 300) {
        return is_array($body) ? $body : ['status' => 'unknown'];
    }

    return askagento_install_api_response_error($response, __('Failed to check status', 'askagento'));
}

/**
 * Install throughput benchmark (~1.2 products/s).
 */
function askagento_install_benchmark_products() {
    return 60;
}

function askagento_install_benchmark_seconds() {
    return 50;
}

/**
 * Catalogue products processed per second (benchmark rate).
 *
 * @return float
 */
function askagento_install_products_per_second() {
    $products = askagento_install_benchmark_products();
    $seconds  = askagento_install_benchmark_seconds();

    return $seconds > 0 ? ($products / $seconds) : 0.0;
}

/**
 * Approximate catalogue install duration in seconds (benchmark-scaled).
 *
 * @param int $product_count Published products in the catalogue.
 * @return int Whole seconds.
 */
function askagento_estimate_install_seconds($product_count) {
    $count = max(0, (int) $product_count);
    if ($count === 0) {
        return 0;
    }

    $seconds = (int) round(
        $count * askagento_install_benchmark_seconds() / askagento_install_benchmark_products()
    );

    return max(1, $seconds);
}

/**
 * Human-readable install ETA for admin notices.
 *
 * @param int $seconds Estimated duration in seconds.
 * @return string
 */
function askagento_format_install_eta_display($seconds) {
    $seconds = max(0, (int) $seconds);

    if ($seconds < 60) {
        return sprintf(
            /* translators: %d: seconds */
            _n('%d second', '%d seconds', $seconds, 'askagento'),
            $seconds
        );
    }

    $minutes     = intdiv($seconds, 60);
    $remain_secs = $seconds % 60;

    if ($remain_secs === 0) {
        return sprintf(
            /* translators: %d: minutes */
            _n('%d minute', '%d minutes', $minutes, 'askagento'),
            $minutes
        );
    }

    return sprintf(
        /* translators: 1: minutes, 2: seconds */
        __('%1$d min %2$d sec', 'askagento'),
        $minutes,
        $remain_secs
    );
}

/**
 * Formatted install throughput for admin notices (one decimal).
 *
 * @return string
 */
function askagento_format_install_rate_display() {
    return number_format(askagento_install_products_per_second(), 1, '.', '');
}

/**
 * Product count from the most recent install/sync request.
 *
 * @return int
 */
function askagento_get_install_product_count() {
    $stored = (int) get_option('askagento_install_last_product_count', 0);
    if ($stored > 0) {
        return $stored;
    }

    if (!function_exists('askagento_get_all_products_for_install')) {
        return 0;
    }

    return count(askagento_get_all_products_for_install());
}

/**
 * Classify remote install status for admin polling (processing / completed / failed).
 *
 * @param array<string, mixed> $status_payload Response from askagento_check_install_status().
 * @return string One of: processing, completed, failed, unknown.
 */
function askagento_parse_install_status_phase(array $status_payload) {
    $message = '';
    if (!empty($status_payload['status_message']) && is_string($status_payload['status_message'])) {
        $message = strtolower(trim($status_payload['status_message']));
    }

    if ($message !== '') {
        if (strpos($message, 'completed') !== false) {
            return 'completed';
        }
        if (strpos($message, 'fail') !== false || strpos($message, 'error') !== false) {
            return 'failed';
        }
        if (strpos($message, 'processing') !== false || strpos($message, 'started') !== false) {
            return 'processing';
        }
    }

    return 'unknown';
}

/**
 * Clear persisted dismissal so the processing banner shows again on the next sync.
 */
function askagento_reset_install_processing_notice_dismissed() {
    delete_option('askagento_install_processing_notice_dismissed');
}

/**
 * Whether the global "catalogue is syncing" admin notice should render.
 */
function askagento_should_show_install_processing_notice() {
    if (!current_user_can('manage_options')) {
        return false;
    }

    $last_status = (string) get_option('askagento_install_last_status', '');
    if (!in_array($last_status, ['processing', 'unknown'], true)) {
        return false;
    }

    if (get_option('askagento_install_processing_notice_dismissed')) {
        return false;
    }

    if (function_exists('askagento_is_plugins_admin_screen') && askagento_is_plugins_admin_screen()) {
        if (!empty($_GET['askagento_install']) && sanitize_key(wp_unslash($_GET['askagento_install'])) === 'started') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            return false;
        }
    }

    $page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    $tab  = isset($_GET['tab']) ? sanitize_key((string) wp_unslash($_GET['tab'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    if ($page === 'askagento-settings' && in_array($tab, ['status', 'sync'], true)) {
        return false;
    }

    return true;
}

/**
 * Enrich install status API payload for admin UI + polling.
 *
 * @param array<string, mixed> $status_payload Response from askagento_check_install_status().
 * @return array<string, mixed>
 */
function askagento_format_install_status_for_admin(array $status_payload) {
    $phase = askagento_parse_install_status_phase($status_payload);
    $status_payload['install_phase'] = $phase;

    $install_for_status = askagento_get_install_settings();
    $status_payload['api_key'] = isset($install_for_status['api_key']) ? (string) $install_for_status['api_key'] : '';

    if (!empty($status_payload['installed_at'])) {
        $status_payload['installed_at_display'] = askagento_format_datetime_for_display($status_payload['installed_at']);
    }

    $previous_phase = (string) get_option('askagento_install_last_status', '');
    if ($phase === 'completed' && $previous_phase !== 'completed') {
        update_option('askagento_install_completion_notice_pending', 1, false);
    }

    update_option('askagento_install_last_status', $phase, true);

    return $status_payload;
}

/**
 * Admin AJAX: installation status polling and related settings UI helpers.
 */
add_action('wp_ajax_askagento_check_install_status', function() {
    if (!check_ajax_referer('askagento_admin', 'nonce', false)) {
        wp_send_json_error(['message' => __('Security check failed. Please reload the page.', 'askagento')], 403);
    }

    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Permission denied', 'askagento')]);
    }

    $site_id = get_option('askagento_install_site_id', '');
    if (empty($site_id)) {
        wp_send_json_error(['message' => __('No installation ID found', 'askagento')]);
    }

    $result = askagento_check_install_status($site_id);
    if (is_wp_error($result)) {
        $last_status = (string) get_option('askagento_install_last_status', '');
        if ($result->get_error_code() === 'not_found' && in_array($last_status, ['processing', 'unknown'], true)) {
            wp_send_json_success([
                'install_phase'   => 'processing',
                'status_message'  => __('Installation is starting…', 'askagento'),
            ]);
        }
        wp_send_json_error(['message' => $result->get_error_message()]);
    }

    $result = askagento_format_install_status_for_admin($result);
    wp_send_json_success($result);
});

add_action('wp_ajax_askagento_dismiss_install_completion_notice', function() {
    if (!check_ajax_referer('askagento_admin', 'nonce', false)) {
        wp_send_json_error(['message' => __('Security check failed. Please reload the page.', 'askagento')], 403);
    }

    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Permission denied', 'askagento')]);
    }

    delete_option('askagento_install_completion_notice_pending');
    wp_send_json_success();
});

add_action('wp_ajax_askagento_dismiss_install_processing_notice', function() {
    if (!check_ajax_referer('askagento_admin', 'nonce', false)) {
        wp_send_json_error(['message' => __('Security check failed. Please reload the page.', 'askagento')], 403);
    }

    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Permission denied', 'askagento')]);
    }

    update_option('askagento_install_processing_notice_dismissed', 1, false);
    wp_send_json_success();
});

add_action('wp_ajax_askagento_format_admin_date', function () {
    if (!check_ajax_referer('askagento_admin', 'nonce', false)) {
        wp_send_json_error(['message' => __('Security check failed. Please reload the page.', 'askagento')], 403);
    }

    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Permission denied', 'askagento')]);
    }

    $date = isset($_POST['date']) ? sanitize_text_field(wp_unslash($_POST['date'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
    if ($date === '') {
        wp_send_json_error(['message' => __('No date provided.', 'askagento')]);
    }

    wp_send_json_success([
        'formatted' => askagento_format_date_for_display($date),
    ]);
});

add_action('wp_ajax_askagento_get_update_schedule', function () {
    if (!check_ajax_referer('askagento_admin', 'nonce', false)) {
        wp_send_json_error(['message' => __('Security check failed. Please reload the page.', 'askagento')], 403);
    }

    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Permission denied', 'askagento')]);
    }

    $site_id = get_option('askagento_install_site_id', '');
    $result  = askagento_build_update_schedule_payload($site_id);
    if (is_wp_error($result)) {
        wp_send_json_error(['message' => $result->get_error_message()]);
    }

    wp_send_json_success($result);
});

/**
 * Enqueue install-status polling script on admin screens that need it.
 *
 * @param bool $auto_start Begin polling as soon as the script loads.
 */
function askagento_enqueue_install_status_poll_script($auto_start = false) {
    wp_enqueue_style(
        'askagento-admin',
        ASKAGENTO_PLUGIN_URL . 'assets/css/admin.css',
        [],
        askagento_asset_version('assets/css/admin.css')
    );

    wp_enqueue_script(
        'askagento-admin-install-status',
        ASKAGENTO_PLUGIN_URL . 'assets/js/admin-install-status.js',
        ['jquery'],
        askagento_asset_version('assets/js/admin-install-status.js'),
        true
    );

    wp_localize_script(
        'askagento-admin-install-status',
        'askagentoInstallStatus',
        [
            'ajaxUrl'    => admin_url('admin-ajax.php'),
            'nonce'      => wp_create_nonce('askagento_admin'),
            'autoStart'  => (bool) $auto_start,
            'intervalMs' => 4000,
            'maxMs'      => 900000,
            'i18n'       => [
                'checking'   => __('Checking installation status…', 'askagento'),
                'processing' => __('Processing your product catalogue…', 'askagento'),
                'completed'  => __('AskAgento installation complete. Your product catalogue is ready for AI search.', 'askagento'),
                'completedBanner' => __('AskAgento installation complete.', 'askagento'),
                'completedDetail' => __('Your product catalogue is ready for AI search.', 'askagento'),
                'failed'     => __('Installation failed. Please try again or contact support.', 'askagento'),
                'timeout'    => __('Installation is taking longer than expected. Use Check status to try again.', 'askagento'),
                'error'      => __('Could not check installation status.', 'askagento'),
                'statusLine' => __('Status', 'askagento'),
                'installedAt'=> __('Installed at', 'askagento'),
            ],
        ]
    );
}

add_action('admin_enqueue_scripts', 'askagento_admin_enqueue_install_status_poll');

/**
 * Load polling script on Plugins (post-install notice) and Settings screens.
 *
 * @param string $hook_suffix Current admin page hook.
 */
function askagento_admin_enqueue_install_status_poll($hook_suffix) {
    if (!is_admin() || !current_user_can('manage_options')) {
        return;
    }

    $auto_start   = false;
    $last_status  = (string) get_option('askagento_install_last_status', '');
    $is_processing = in_array($last_status, ['processing', 'unknown'], true);

    if (function_exists('askagento_is_plugins_admin_screen') && askagento_is_plugins_admin_screen()) {
        if (!empty($_GET['askagento_install']) && sanitize_key(wp_unslash($_GET['askagento_install'])) === 'started') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            askagento_enqueue_install_status_poll_script(true);
            return;
        }
    }

    if ($hook_suffix === 'askagento_page_askagento-settings') {
        $tab = isset($_GET['tab']) ? sanitize_key((string) wp_unslash($_GET['tab'])) : 'overview'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        if (in_array($tab, ['status', 'sync'], true)) {
            $auto_start = $is_processing;
            askagento_enqueue_install_status_poll_script($auto_start);
            return;
        }
    }

    if (function_exists('askagento_should_show_install_processing_notice') && askagento_should_show_install_processing_notice()) {
        askagento_enqueue_install_status_poll_script(false);
    }
}

/**
 * Map a failed AskAgento HTTP response to a WP_Error with a specific admin message.
 *
 * @param array|\WP_Error $response  wp_remote_* result.
 * @param string          $default_message Fallback when the body has no detail.
 * @param string          $bundle_missing_message When the API reports no recommendation file yet.
 * @return WP_Error
 */
function askagento_install_api_response_error($response, $default_message, $bundle_missing_message = '') {
    if (is_wp_error($response)) {
        return $response;
    }

    $code = (int) wp_remote_retrieve_response_code($response);
    $body = json_decode(wp_remote_retrieve_body($response), true);
    $api_error = '';

    if (is_array($body)) {
        if (!empty($body['error']) && is_string($body['error'])) {
            $api_error = trim($body['error']);
        } elseif (!empty($body['message']) && is_string($body['message'])) {
            $api_error = trim($body['message']);
        }
    }

    if ($code === 401) {
        if ($api_error !== '') {
            return new WP_Error(
                'unauthorized',
                sprintf(
                    /* translators: %s: API error text */
                    __('Invalid API key: %s', 'askagento'),
                    $api_error
                )
            );
        }

        return new WP_Error(
            'unauthorized',
            __('Invalid or missing Discovery API key. Check Settings → Installation.', 'askagento')
        );
    }

    if ($code === 404) {
        if ($api_error !== '' && stripos($api_error, 'recommendation') !== false) {
            $message = $bundle_missing_message !== ''
                ? $bundle_missing_message
                : $default_message;

            return new WP_Error('not_found', $message);
        }

        if ($api_error !== '' && strtolower($api_error) !== 'not found') {
            return new WP_Error(
                'not_found',
                sprintf(
                    /* translators: %s: API error text */
                    __('AskAgento API: %s', 'askagento'),
                    $api_error
                )
            );
        }

        return new WP_Error(
            'not_found',
            __('Site not found or the API key does not match this installation. Check Settings → Installation (site ID and API key).', 'askagento')
        );
    }

    if ($api_error !== '') {
        return new WP_Error(
            'api_error',
            sprintf(
                /* translators: 1: default message, 2: API error text */
                __('%1$s (%2$s)', 'askagento'),
                $default_message,
                $api_error
            )
        );
    }

    if ($code > 0) {
        return new WP_Error(
            'api_error',
            sprintf(
                /* translators: 1: default message, 2: HTTP status code */
                __('%1$s (HTTP %2$d)', 'askagento'),
                $default_message,
                $code
            )
        );
    }

    return new WP_Error('api_error', $default_message);
}

/**
 * Human-readable label for a plan slug from the API (e.g. pro → Pro).
 *
 * @param string|null $plan_slug Stored slug such as free, basic, pro.
 * @return string Empty when unknown or blank.
 */
function askagento_format_plan_label($plan_slug) {
    if ($plan_slug === null || $plan_slug === '') {
        return '';
    }
    $s = strtolower(trim((string) $plan_slug));
    $map = [
        'free'  => __('Free', 'askagento'),
        'basic' => __('Basic', 'askagento'),
        'pro'   => __('Pro', 'askagento'),
    ];
    if (isset($map[$s])) {
        return $map[$s];
    }

    return ucfirst($s);
}

/**
 * Catalogue update cadence for a plan slug.
 *
 * @param string|null $plan_slug free, basic, pro, etc.
 * @return string "daily" or "monthly"
 */
function askagento_get_update_cadence_for_plan($plan_slug) {
    $s = strtolower(trim((string) $plan_slug));
    if ($s === 'basic' || $s === 'pro') {
        return 'daily';
    }

    return 'monthly';
}

/**
 * Human-readable update frequency for a plan.
 *
 * @param string|null $plan_slug
 * @return string
 */
function askagento_format_update_cadence_label($plan_slug) {
    if (askagento_get_update_cadence_for_plan($plan_slug) === 'daily') {
        return __('once per day', 'askagento');
    }

    return __('once per month', 'askagento');
}

/**
 * Whole days remaining in a free trial, from the API's trial_ends_at timestamp.
 *
 * @param string|null $trial_ends_at ISO-8601 timestamp (assumed UTC when no offset).
 * @return int|null Days remaining (>= 0, rounded up), or null when unknown/invalid.
 */
function askagento_trial_days_remaining($trial_ends_at) {
    $raw = trim((string) $trial_ends_at);
    if ($raw === '') {
        return null;
    }
    $normalized = function_exists('askagento_normalize_installed_at_utc_string')
        ? askagento_normalize_installed_at_utc_string($raw)
        : $raw;
    $ends = strtotime($normalized);
    if ($ends === false) {
        return null;
    }
    $now = time();
    if ($ends <= $now) {
        return 0;
    }

    return (int) ceil(($ends - $now) / DAY_IN_SECONDS);
}

/**
 * Ensure API datetimes without a timezone suffix are treated as UTC.
 *
 * @param string|null $installed_at Raw value from the API.
 * @return string
 */
function askagento_normalize_installed_at_utc_string($installed_at) {
    $raw = trim((string) $installed_at);
    if ($raw === '') {
        return '';
    }
    if (preg_match('/[Zz]$/', $raw) || preg_match('/[+-]\d{2}:?\d{2}$/', $raw)) {
        return $raw;
    }
    if (preg_match('/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/', $raw)) {
        return preg_replace('/\s+/', 'T', $raw) . 'Z';
    }

    return $raw;
}

/**
 * Parse install status `installed_at` as UTC.
 *
 * @param string|null $installed_at ISO 8601 or strtotime-compatible value.
 * @return DateTimeImmutable|null
 */
function askagento_parse_installed_at_utc($installed_at) {
    $raw = askagento_normalize_installed_at_utc_string($installed_at);
    if ($raw === '') {
        return null;
    }

    try {
        return new DateTimeImmutable($raw, new DateTimeZone('UTC'));
    } catch (Exception $e) {
        $ts = strtotime($raw . ' UTC');
        if ($ts === false || $ts <= 0) {
            return null;
        }

        return (new DateTimeImmutable('@' . $ts))->setTimezone(new DateTimeZone('UTC'));
    }
}

/**
 * WordPress admin date format (site timezone).
 */
function askagento_get_admin_date_format() {
    return (string) get_option('date_format');
}

/**
 * WordPress admin date + time format (site timezone).
 */
function askagento_get_admin_datetime_format() {
    return askagento_get_admin_date_format() . ' ' . (string) get_option('time_format');
}

/**
 * Format an API UTC timestamp as a date in the WordPress site timezone.
 *
 * @param string|null $datetime_raw
 * @return string
 */
function askagento_format_date_for_display($datetime_raw) {
    $raw = trim((string) $datetime_raw);
    if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $raw)) {
        $raw .= 'T00:00:00Z';
    }

    $dt = askagento_parse_installed_at_utc($raw);
    if ($dt === null) {
        return trim((string) $datetime_raw);
    }

    return wp_date(askagento_get_admin_date_format(), $dt->getTimestamp());
}

/**
 * Format an API UTC timestamp for display in the WordPress site timezone.
 *
 * @param string|null $datetime_raw
 * @return string
 */
function askagento_format_datetime_for_display($datetime_raw) {
    $dt = askagento_parse_installed_at_utc($datetime_raw);
    if ($dt === null) {
        return trim((string) $datetime_raw);
    }

    return wp_date(askagento_get_admin_datetime_format(), $dt->getTimestamp());
}

/**
 * Next catalogue update datetimes from installation anchor and plan cadence.
 *
 * @param string|null   $installed_at Install status `installed_at`.
 * @param string|null   $plan_slug    Plan slug (free → monthly; basic/pro → daily).
 * @param int           $count        Number of future dates to return.
 * @return DateTimeImmutable[]
 */
function askagento_get_next_update_dates($installed_at, $plan_slug, $count = 3) {
    $count = max(1, (int) $count);
    $install = askagento_parse_installed_at_utc($installed_at);
    if ($install === null) {
        return [];
    }

    $now      = new DateTimeImmutable('now', new DateTimeZone('UTC'));
    $cadence  = askagento_get_update_cadence_for_plan($plan_slug);
    $dates    = [];

    if ($cadence === 'daily') {
        $cursor = $install;
        $guard  = 0;
        while ($cursor <= $now && $guard < 50000) {
            $cursor = $cursor->modify('+1 day');
            ++$guard;
        }
        for ($i = 0; $i < $count; $i++) {
            $dates[] = $cursor;
            $cursor  = $cursor->modify('+1 day');
        }

        return $dates;
    }

    $day_of_month = (int) $install->format('j');
    $time         = $install->format('H:i:s');

    $cursor = $now->setTime(0, 0, 0);
    $y      = (int) $cursor->format('Y');
    $m      = (int) $cursor->format('n');
    $days   = (int) $cursor->format('t');
    $cursor = $cursor->setDate($y, $m, min($day_of_month, $days));
    $cursor = DateTimeImmutable::createFromFormat(
        'Y-m-d H:i:s',
        $cursor->format('Y-m-d') . ' ' . $time,
        new DateTimeZone('UTC')
    );
    if ($cursor === false) {
        return [];
    }
    if ($cursor <= $now) {
        $cursor = $cursor->modify('first day of next month');
        $days   = (int) $cursor->format('t');
        $cursor = $cursor->setDate(
            (int) $cursor->format('Y'),
            (int) $cursor->format('n'),
            min($day_of_month, $days)
        );
        $cursor = DateTimeImmutable::createFromFormat(
            'Y-m-d H:i:s',
            $cursor->format('Y-m-d') . ' ' . $time,
            new DateTimeZone('UTC')
        );
        if ($cursor === false) {
            return [];
        }
    }

    for ($i = 0; $i < $count; $i++) {
        $dates[] = $cursor;
        $cursor  = $cursor->modify('first day of next month');
        $days    = (int) $cursor->format('t');
        $cursor  = $cursor->setDate(
            (int) $cursor->format('Y'),
            (int) $cursor->format('n'),
            min($day_of_month, $days)
        );
        $cursor  = DateTimeImmutable::createFromFormat(
            'Y-m-d H:i:s',
            $cursor->format('Y-m-d') . ' ' . $time,
            new DateTimeZone('UTC')
        );
        if ($cursor === false) {
            break;
        }
    }

    return $dates;
}

/**
 * Advance one monthly catalogue-update slot from a given slot (install day-of-month + time).
 *
 * @param DateTimeImmutable $slot    Current slot.
 * @param DateTimeImmutable $install Install anchor (day-of-month + time).
 * @return DateTimeImmutable|null
 */
function askagento_advance_monthly_update_slot(DateTimeImmutable $slot, DateTimeImmutable $install) {
    $day_of_month = (int) $install->format('j');
    $time         = $install->format('H:i:s');
    $cursor       = $slot->modify('first day of next month');
    $days         = (int) $cursor->format('t');
    $cursor       = $cursor->setDate(
        (int) $cursor->format('Y'),
        (int) $cursor->format('n'),
        min($day_of_month, $days)
    );

    return DateTimeImmutable::createFromFormat(
        'Y-m-d H:i:s',
        $cursor->format('Y-m-d') . ' ' . $time,
        new DateTimeZone('UTC')
    ) ?: null;
}

/**
 * Latest scheduled catalogue-update moment at or before $now (install anchor + plan cadence).
 *
 * @param string|null              $installed_at Install status `installed_at`.
 * @param string|null              $plan_slug    Plan slug (free → monthly; basic/pro → daily).
 * @param DateTimeImmutable|null   $now          Reference instant (UTC); default now.
 * @return DateTimeImmutable|null
 */
function askagento_get_last_update_due_before_now($installed_at, $plan_slug, $now = null) {
    $install = askagento_parse_installed_at_utc($installed_at);
    if ($install === null) {
        return null;
    }

    $now = $now instanceof DateTimeImmutable
        ? $now
        : new DateTimeImmutable('now', new DateTimeZone('UTC'));

    if (askagento_get_update_cadence_for_plan($plan_slug) === 'daily') {
        $slot  = $install;
        $guard = 0;
        while ($slot <= $now && $guard < 50000) {
            $next = $slot->modify('+1 day');
            if ($next > $now) {
                return $slot;
            }
            $slot = $next;
            ++$guard;
        }

        return $slot <= $now ? $slot : null;
    }

    $slot  = $install;
    $guard = 0;
    while ($slot <= $now && $guard < 5000) {
        $next = askagento_advance_monthly_update_slot($slot, $install);
        if ($next === null || $next > $now) {
            return $slot;
        }
        $slot = $next;
        ++$guard;
    }

    return $slot <= $now ? $slot : null;
}

/**
 * Whether a catalogue auto-sync is due for the current plan and last fulfilled slot.
 *
 * @param string|null            $installed_at
 * @param string|null            $plan_slug
 * @param DateTimeImmutable|null $last_fulfilled_slot Last slot already synced (UTC).
 * @param DateTimeImmutable|null $now
 * @return bool
 */
function askagento_catalogue_sync_is_due_for_plan($installed_at, $plan_slug, $last_fulfilled_slot = null, $now = null) {
    $due = askagento_get_last_update_due_before_now($installed_at, $plan_slug, $now);
    if ($due === null) {
        return false;
    }
    if (!$last_fulfilled_slot instanceof DateTimeImmutable) {
        return true;
    }

    return $last_fulfilled_slot < $due;
}

/**
 * POST current WooCommerce catalogue to Askagento (`POST /update`).
 *
 * @return array{synced_products: int, remote: array|null}|WP_Error
 */
function askagento_sync_product_data_to_api() {
    if (!function_exists('askagento_get_install_settings') || !function_exists('askagento_get_all_products_for_install')) {
        return new WP_Error('unavailable', __('Sync is unavailable.', 'askagento'));
    }

    $settings = askagento_get_install_settings();
    $site_id  = trim((string) get_option('askagento_install_site_id', ''));
    $api_key  = isset($settings['api_key']) ? trim((string) $settings['api_key']) : '';
    $api_url  = rtrim((string) ($settings['api_url'] ?? ''), '/');

    if ($site_id === '' || $api_key === '' || $api_url === '') {
        return new WP_Error(
            'not_installed',
            __('Complete Askagento installation first (site ID + Discovery API key).', 'askagento')
        );
    }

    $products = askagento_get_all_products_for_install();
    if (empty($products)) {
        return new WP_Error('no_products', __('No products found to sync.', 'askagento'));
    }

    $payload = [
        'domain'              => wp_parse_url(site_url(), PHP_URL_HOST),
        'plugin_version'      => ASKAGENTO_VERSION,
        'woocommerce_version' => defined('WC_VERSION') ? WC_VERSION : 'unknown',
        'products'            => $products,
        'total_products'      => count($products),
        'system'              => 'woocommerce',
    ];

    $response = wp_remote_post(
        $api_url . '/update',
        [
            'method'  => 'POST',
            'timeout' => 60,
            'headers' => [
                'Content-Type'          => 'application/json',
                'X-Askagento-Site-Id'   => $site_id,
                'X-Askagento-Api-Key'   => $api_key,
            ],
            'body'    => wp_json_encode($payload),
        ]
    );

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

    $status = (int) wp_remote_retrieve_response_code($response);
    $body   = json_decode(wp_remote_retrieve_body($response), true);

    if ($status >= 200 && $status < 300) {
        return [
            'synced_products' => count($products),
            'remote'          => is_array($body) ? $body : null,
        ];
    }

    $message = __('Sync failed.', 'askagento');
    if (is_array($body) && !empty($body['error'])) {
        $message = (string) $body['error'];
    }

    return new WP_Error('api_error', $message, ['status' => $status]);
}

/**
 * Build update schedule payload (install date, plan cadence, next update times).
 *
 * @param string $site_id Installation site identifier.
 * @return array<string, mixed>|WP_Error
 */
function askagento_build_update_schedule_payload($site_id) {
    $site_id = trim((string) $site_id);
    if ($site_id === '') {
        return new WP_Error('no_site', __('No installation ID found', 'askagento'));
    }

    $install_settings = askagento_get_install_settings();
    $api_key          = isset($install_settings['api_key']) ? trim((string) $install_settings['api_key']) : '';
    if ($api_key === '') {
        return new WP_Error(
            'missing_api_key',
            __('Discovery API key is missing; cannot load update schedule.', 'askagento')
        );
    }

    $status = askagento_check_install_status($site_id);
    if (is_wp_error($status)) {
        return $status;
    }

    $installed_at = isset($status['installed_at']) ? trim((string) $status['installed_at']) : '';
    if ($installed_at === '') {
        return new WP_Error(
            'no_installed_at',
            __('Install status API returned no installation date.', 'askagento')
        );
    }

    $plan_slug  = 'free';
    $plan_label = '';
    $plan_res   = askagento_fetch_plan($site_id);
    if (is_wp_error($plan_res)) {
        return $plan_res;
    }
    if (is_array($plan_res)) {
        $p = $plan_res['plan'] ?? null;
        if ($p !== null && $p !== '') {
            $plan_slug = (string) $p;
        }
        if (!empty($plan_res['plan_label'])) {
            $plan_label = (string) $plan_res['plan_label'];
        }
    }
    if ($plan_label === '') {
        $plan_label = askagento_format_plan_label($plan_slug);
    }

    $installed_display = askagento_format_datetime_for_display($installed_at);

    $next_updates = [];
    foreach (askagento_get_next_update_dates($installed_at, $plan_slug, 3) as $update_dt) {
        $next_updates[] = wp_date(askagento_get_admin_date_format(), $update_dt->getTimestamp());
    }

    return [
        'installed_at'         => $installed_at,
        'installed_at_display' => $installed_display,
        'plan_slug'            => $plan_slug,
        'plan_label'           => $plan_label,
        'cadence_label'        => askagento_format_update_cadence_label($plan_slug),
        'next_updates'         => $next_updates,
    ];
}

/**
 * Map known Askagento API error strings to user-facing (translatable) messages.
 *
 * @param string $raw_message Message from API JSON `error` field.
 * @return string
 */
function askagento_remote_error_to_message($raw_message) {
    $m = is_string($raw_message) ? trim($raw_message) : '';

    if ($m === 'AI search quota exceeded') {
        return __('AI search quota exceeded', 'askagento');
    }

    if ($m === 'AI token quota exceeded') {
        return __('AI token quota exceeded', 'askagento');
    }

    return $m;
}

/**
 * Current subscription plan from the AskAgento API install record.
 *
 * @param string $site_id Installation site identifier.
 * @return array<string, mixed>|WP_Error Keys: site_id, plan (string|null), plan_label (string|null).
 */
function askagento_fetch_plan($site_id) {
    $settings = askagento_get_install_settings();

    if (!askagento_install_api_is_enabled()) {
        return new WP_Error('not_enabled', __('Installation API not enabled', 'askagento'));
    }

    $site_id = trim((string) $site_id);
    if ($site_id === '') {
        return new WP_Error('missing_installation_id', __('No installation ID found', 'askagento'));
    }

    $api_key = isset($settings['api_key']) ? trim((string) $settings['api_key']) : '';
    if ($api_key === '') {
        return new WP_Error('missing_api_key', __('Discovery API key is missing; cannot load plan.', 'askagento'));
    }

    $args = [
        'method'  => 'GET',
        'timeout' => 15,
        'headers' => [
            'Content-Type'         => 'application/json',
            'X-Askagento-Site-Id'  => $site_id,
            'X-Askagento-Api-Key'  => $api_key,
        ],
    ];

    $base = rtrim((string) $settings['api_url'], '/');
    $url  = $base . '/plan';
    $response = wp_remote_get($url, $args);
    if (is_wp_error($response)) {
        return $response;
    }

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

    if ($code >= 200 && $code < 300) {
        return is_array($body) ? $body : ['site_id' => $site_id, 'plan' => null, 'plan_label' => null];
    }

    return new WP_Error('api_error', __('Failed to load plan', 'askagento'));
}

/**
 * Plan and usage limits from the AskAgento API install record.
 *
 * @param string $site_id Installation site identifier.
 * @return array<string, mixed>|WP_Error Keys: site_id, plan, plan_label, limits, plan_max_limits (tier caps).
 */
function askagento_fetch_usage($site_id) {
    $settings = askagento_get_install_settings();

    if (!askagento_install_api_is_enabled()) {
        return new WP_Error('not_enabled', __('Installation API not enabled', 'askagento'));
    }

    $site_id = trim((string) $site_id);
    if ($site_id === '') {
        return new WP_Error('missing_installation_id', __('No installation ID found', 'askagento'));
    }

    $api_key = isset($settings['api_key']) ? trim((string) $settings['api_key']) : '';
    if ($api_key === '') {
        return new WP_Error('missing_api_key', __('Discovery API key is missing; cannot load usage.', 'askagento'));
    }

    $args = [
        'method'  => 'GET',
        'timeout' => 15,
        'headers' => [
            'Content-Type'         => 'application/json',
            'X-Askagento-Site-Id'  => $site_id,
            'X-Askagento-Api-Key'  => $api_key,
        ],
    ];

    $base = rtrim((string) $settings['api_url'], '/');
    $url  = $base . '/usage';
    $response = wp_remote_get($url, $args);
    if (is_wp_error($response)) {
        return $response;
    }

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

    if ($code >= 200 && $code < 300) {
        return is_array($body) ? $body : ['site_id' => $site_id, 'plan' => null, 'plan_label' => null, 'limits' => null, 'plan_max_limits' => null];
    }

    return new WP_Error('api_error', __('Failed to load usage', 'askagento'));
}

/**
 * Feature slugs enabled for the current site's plan.
 *
 * Results are cached in a transient for 1 hour so that individual module
 * availability checks (askagento_feature_is_enabled) do not trigger extra API calls.
 * Cache is cleared on plugin activation / install refresh.
 *
 * Source of truth: /plugin/features/{plan} SSM parameters defined in
 * PluginConfiguration.yaml, returned by GET /usage as the `features` array.
 *
 * @param string $site_id
 * @return string[] List of enabled feature slugs, or [] on error.
 */
function askagento_fetch_features($site_id) {
    $site_id = trim((string) $site_id);
    if ($site_id === '') {
        return [];
    }

    $transient_key = 'askagento_features_' . md5($site_id);
    $cached = get_transient($transient_key);
    if (is_array($cached)) {
        return $cached;
    }

    $usage = askagento_fetch_usage($site_id);
    if (is_wp_error($usage) || !is_array($usage)) {
        return [];
    }

    $features = isset($usage['features']) && is_array($usage['features'])
        ? array_values(array_filter(array_map('strval', $usage['features'])))
        : [];

    set_transient($transient_key, $features, HOUR_IN_SECONDS);
    return $features;
}

/**
 * Cached per-plan product-limit state for the current site (from GET /usage).
 *
 * Source of truth is the API, which compares the shop's catalogue size to the
 * plan's /plugin/{plan}/products cap. Cached for an hour (like the feature list)
 * so feature checks don't trigger an API call on every page load.
 *
 * @param string $site_id
 * @return array{over_limit: bool, product_count: int|null, max_products: int|null, plan_label: string}
 */
function askagento_get_product_limit_state($site_id) {
    $empty = ['over_limit' => false, 'product_count' => null, 'max_products' => null, 'plan_label' => ''];

    $site_id = trim((string) $site_id);
    if ($site_id === '' || !function_exists('askagento_fetch_usage')) {
        return $empty;
    }

    $transient_key = 'askagento_plimit_' . md5($site_id);
    $cached = get_transient($transient_key);
    if (is_array($cached)) {
        return $cached;
    }

    $usage = askagento_fetch_usage($site_id);
    if (is_wp_error($usage) || !is_array($usage)) {
        return $empty;
    }

    $state = [
        'over_limit'    => !empty($usage['over_product_limit']),
        'product_count' => isset($usage['product_count']) && $usage['product_count'] !== null ? (int) $usage['product_count'] : null,
        'max_products'  => isset($usage['max_products']) && $usage['max_products'] !== null ? (int) $usage['max_products'] : null,
        'plan_label'    => !empty($usage['plan_label']) ? (string) $usage['plan_label'] : '',
    ];
    set_transient($transient_key, $state, HOUR_IN_SECONDS);
    return $state;
}

/**
 * Whether the current site's catalogue exceeds its plan's product cap.
 *
 * When true, all AI features are blocked until the merchant upgrades or reduces
 * the catalogue (see askagento_feature_is_enabled).
 *
 * @return bool
 */
function askagento_is_over_product_limit() {
    $site_id = trim((string) get_option('askagento_install_site_id', ''));
    if ($site_id === '') {
        return false;
    }
    $state = askagento_get_product_limit_state($site_id);
    return !empty($state['over_limit']);
}

/**
 * Whether a specific feature slug is enabled for the current installation.
 *
 * Feature slugs match the SSM values in PluginConfiguration.yaml:
 *   ai_search_text, ai_search_image, ai_recommend_upsell,
 *   ai_recommend_cross_sell, auto_product_filters, auto_labels,
 *   ai_chatbot, ai_agents_mcp
 *
 * Every feature is blocked while the catalogue exceeds the plan's product cap,
 * regardless of which features the plan otherwise includes.
 *
 * @param string $feature_slug
 * @return bool
 */
function askagento_feature_is_enabled($feature_slug) {
    $site_id = trim((string) get_option('askagento_install_site_id', ''));
    if ($site_id === '') {
        return false;
    }
    if (askagento_is_over_product_limit()) {
        return false;
    }
    $features = askagento_fetch_features($site_id);
    return in_array((string) $feature_slug, $features, true);
}

/**
 * Clear the cached feature list and product-limit state for a site
 * (call after plan change or reinstall).
 *
 * @param string $site_id
 */
function askagento_clear_features_cache($site_id) {
    $site_id = trim((string) $site_id);
    if ($site_id !== '') {
        delete_transient('askagento_features_' . md5($site_id));
        delete_transient('askagento_plimit_' . md5($site_id));
    }
}

/**
 * Consumed quota and share of plan max from API remaining counters.
 *
 * @param int|null $remaining Remaining allowance (limits.tokens or limits.searches).
 * @param int|null $max       Plan tier maximum (plan_max_limits).
 * @return array{used: int|null, percent: int|null}
 */
function askagento_usage_from_remaining($remaining, $max) {
    if ($remaining === null || $max === null || (int) $max <= 0) {
        return ['used' => null, 'percent' => null];
    }
    $remaining = max(0, (int) $remaining);
    $max       = (int) $max;
    $used      = max(0, $max - $remaining);
    $percent   = (int) round(($used / $max) * 100);

    return [
        'used'    => $used,
        'percent' => min(100, max(0, $percent)),
    ];
}

/**
 * Format consumed usage for display, e.g. "420 (2%)".
 *
 * @param int|null $used    Consumed units.
 * @param int|null $percent Share of plan max (0–100).
 */
function askagento_format_usage_with_percent($used, $percent) {
    if ($used === null) {
        return '-';
    }
    $formatted = number_format_i18n((int) $used);
    if ($percent !== null) {
        return $formatted . ' (' . number_format_i18n((int) $percent) . '%)';
    }

    return $formatted;
}

/**
 * Consumed unit count and % of plan max from token remaining/max (e.g. messages, labels).
 *
 * @param int|null $remaining_tokens Remaining token quota.
 * @param int|null $max_tokens       Plan tier token maximum.
 * @param int      $tokens_per_unit  Tokens per unit (500 messages, 2000 labels).
 * @return array{used: int|null, percent: int|null}
 */
function askagento_usage_units_from_token_remaining($remaining_tokens, $max_tokens, $tokens_per_unit) {
    $tokens_per_unit = max(1, (int) $tokens_per_unit);
    if ($remaining_tokens === null || $max_tokens === null || (int) $max_tokens <= 0) {
        return ['used' => null, 'percent' => null];
    }

    $max_units = (int) floor((int) $max_tokens / $tokens_per_unit);
    if ($max_units <= 0) {
        return ['used' => null, 'percent' => null];
    }

    $remaining_units = (int) floor(max(0, (int) $remaining_tokens) / $tokens_per_unit);
    $used_units      = max(0, $max_units - $remaining_units);
    $percent         = (int) round(($used_units / $max_units) * 100);

    return [
        'used'    => $used_units,
        'percent' => min(100, max(0, $percent)),
    ];
}

/**
 * Format unit usage for display, e.g. "20 messages (1%)".
 *
 * @param int|null $used    Consumed units.
 * @param int|null $percent Share of plan max (0–100).
 * @param string   $unit    Unit label, e.g. "messages" or "labels".
 */
function askagento_format_unit_usage_with_percent($used, $percent, $unit) {
    if ($used === null) {
        return '-';
    }
    $formatted = number_format_i18n((int) $used) . ' ' . $unit;
    if ($percent !== null) {
        return $formatted . ' (' . number_format_i18n((int) $percent) . '%)';
    }

    return $formatted;
}

/**
 * Site-wide token usage summary for admin screens (from GET /usage).
 *
 * @return array{display: string, remaining: int|null, max: int|null, used: int|null, percent: int|null}
 */
function askagento_get_site_token_usage_summary() {
    $empty = [
        'display'   => '-',
        'remaining' => null,
        'max'       => null,
        'used'      => null,
        'percent'   => null,
    ];

    $site_id = trim((string) get_option('askagento_install_site_id', ''));
    if ($site_id === '' || !function_exists('askagento_fetch_usage')) {
        return $empty;
    }

    $ur = askagento_fetch_usage($site_id);
    if (is_wp_error($ur) || !is_array($ur)) {
        return $empty;
    }

    $limits     = isset($ur['limits']) && is_array($ur['limits']) ? $ur['limits'] : [];
    $max_limits = isset($ur['plan_max_limits']) && is_array($ur['plan_max_limits']) ? $ur['plan_max_limits'] : [];
    $remaining  = isset($limits['tokens']) ? (int) $limits['tokens'] : null;
    $max        = isset($max_limits['tokens']) ? (int) $max_limits['tokens'] : null;
    $usage      = askagento_usage_from_remaining($remaining, $max);

    return [
        'display'   => askagento_format_usage_with_percent($usage['used'], $usage['percent']),
        'remaining' => $remaining,
        'max'       => $max,
        'used'      => $usage['used'],
        'percent'   => $usage['percent'],
    ];
}

/**
 * Attach fresh token_usage to an admin AJAX success payload.
 *
 * @param array<string, mixed> $body
 * @return array<string, mixed>
 */
function askagento_enrich_admin_response_token_usage(array $body) {
    $body['token_usage'] = askagento_get_site_token_usage_summary();

    return $body;
}

