<?php
/**
 * Search result cache.
 *
 * Caches full API search responses keyed by a deterministic hash
 * of the search payload. Reduces repeated Amazon PA-API calls for
 * identical search queries.
 *
 * @package PSFA
 * @since   2.11.0
 */

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

/**
 * Build a deterministic transient key from a search payload.
 *
 * Sorts the payload keys to ensure the same parameters in any order
 * produce the same key.
 *
 * @param array $payload SearchItems API payload.
 * @return string Transient key (44 chars: 'psfa_search_' + 32-char md5).
 */
function psfa_build_search_cache_key( array $payload ): string {
    ksort( $payload );
    return 'psfa_search_' . md5( wp_json_encode( $payload ) );
}

/**
 * Get a cached search result.
 *
 * Returns null on cache miss or when caching is disabled.
 *
 * @param array $payload SearchItems API payload.
 * @return array|null Cached search result or null.
 */
function psfa_get_cached_search( array $payload ): ?array {
    if ( psfa_is_cache_disabled() ) {
        return null;
    }

    $key    = psfa_build_search_cache_key( $payload );
    $cached = get_transient( $key );

    if ( false !== $cached && is_array( $cached ) ) {
        // Strip internal metadata before returning to consumers.
        unset( $cached['_psfa_meta'] );
        return $cached;
    }

    return null;
}

/**
 * Store a search result in the cache.
 *
 * Error responses (containing 'error' key) are NOT cached.
 *
 * @param array $payload       SearchItems API payload.
 * @param array $result        API response array.
 * @param array $search_params Optional original search params (pre-payload).
 *                             Used to capture client-side-only filters
 *                             (e.g. has_deal, sort when skipped) for admin display.
 * @return void
 */
function psfa_set_cached_search( array $payload, array $result, array $search_params = array() ): void {
    if ( psfa_is_cache_disabled() ) {
        return;
    }

    // Do not cache error responses.
    if ( isset( $result['error'] ) ) {
        return;
    }

    // Embed lightweight metadata so the admin Cache page can display
    // meaningful info (keywords, category, page) without parsing the
    // full serialized response.
    //
    // Fields from $payload reflect what was actually sent to the API.
    // Fields from $search_params capture the original user intent
    // (including client-side-only filters like has_deal and sort when
    // it was skipped from the API payload).
    $result['_psfa_meta'] = array(
        'keywords'            => $payload['Keywords'] ?? '',
        'category'            => $payload['SearchIndex'] ?? 'All',
        'page'                => $payload['ItemPage'] ?? 1,
        'sort'                => $payload['SortBy'] ?? ( $search_params['sort'] ?: ( $GLOBALS['psfa_original_sort'] ?? '' ) ),
        'brand'               => $payload['Brand'] ?? '',
        'condition'           => $payload['Condition'] ?? '',
        'merchant'            => $payload['Merchant'] ?? '',
        'availability'        => $payload['Availability'] ?? '',
        'min_price'           => $payload['MinPrice'] ?? 0,
        'max_price'           => $payload['MaxPrice'] ?? 0,
        'min_saving_percent'  => $payload['MinSavingPercent'] ?? 0,
        'min_reviews_rating'  => $payload['MinReviewsRating'] ?? 0,
        'has_deal'            => $search_params['has_deal'] ?? '',
        'created_at'          => time(),
    );

    // Capture optional purpose flag (e.g. 'filter_options' for unfiltered
    // passes that only exist to populate the brands dropdown).
    global $psfa_cache_purpose;
    if ( ! empty( $psfa_cache_purpose ) ) {
        $result['_psfa_meta']['purpose'] = $psfa_cache_purpose;
    }

    $key = psfa_build_search_cache_key( $payload );
    set_transient( $key, $result, 30 * MINUTE_IN_SECONDS );
}

/**
 * Clear all search cache transients.
 *
 * @return int Number of transients deleted.
 */
function psfa_clear_search_cache(): int {
    global $wpdb, $psfa_test_transients;

    $count = 0;

    if ( isset( $psfa_test_transients ) && is_array( $psfa_test_transients ) ) {
        // Test environment.
        foreach ( array_keys( $psfa_test_transients ) as $key ) {
            if ( 0 === strpos( $key, 'psfa_search_' ) ) {
                unset( $psfa_test_transients[ $key ] );
                ++$count;
            }
        }
        return $count;
    }

    // Production: bulk SQL delete.
    if ( isset( $wpdb ) ) {
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $count = (int) $wpdb->query(
            "DELETE FROM {$wpdb->options}
             WHERE option_name LIKE '_transient_psfa_search_%'
                OR option_name LIKE '_transient_timeout_psfa_search_%'"
        );
        $count = (int) floor( $count / 2 );
    }

    return $count;
}

/**
 * Clear ALL plugin caches (product + search).
 *
 * @return array Associative array with 'products' and 'searches' counts.
 */
function psfa_clear_all_caches(): array {
    return array(
        'products' => psfa_clear_product_cache(),
        'searches' => psfa_clear_search_cache(),
    );
}

/**
 * Check if caching is disabled.
 *
 * Note: PSFA_MOCK_MODE is NOT checked here because mock mode is
 * handled earlier in core.php (returns mock data before reaching
 * the cache layer). This function only checks explicit cache disabling.
 *
 * @return bool True if caching should be bypassed.
 */
function psfa_is_cache_disabled(): bool {
    // Test-friendly override (can be set/unset unlike constants).
    global $psfa_cache_disabled_override;
    if ( ! empty( $psfa_cache_disabled_override ) ) {
        return true;
    }

    // Explicit disable constant.
    if ( defined( 'PSFA_DISABLE_CACHE' ) && PSFA_DISABLE_CACHE ) {
        return true;
    }

    return false;
}
