<?php

/**
 * Adaptive cache preload / keep-warm throttle.
 *
 * Shared, pure decision logic so both the free keep-warm cron
 * (ModernCacheModule) and the premium Action Scheduler batch loop
 * (CachePreloadModule) reason about server load the same way.
 *
 * Safe on hosts where sys_getloadavg() is unavailable (Windows, or where
 * the function is disabled): support is reported as false and throttling
 * never fires, so preloading keeps working exactly as before.
 *
 * @package ProRank\SEO\Modules\Performance
 */

declare(strict_types=1);

namespace ProRank\SEO\Modules\Performance;

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

/**
 * CachePreloadThrottle helper.
 */
final class CachePreloadThrottle {

    /**
     * Default 1-minute load average ceiling before batches are deferred.
     */
    public const DEFAULT_MAX_LOAD = 4.0;

    /**
     * Default delay (seconds) inserted between batches when throttling.
     */
    public const DEFAULT_BATCH_DELAY = 45;

    /**
     * Lower / upper guard rails for the configurable batch delay.
     */
    public const MIN_BATCH_DELAY = 5;
    public const MAX_BATCH_DELAY = 600;

    /**
     * Whether this host can report a load average.
     *
     * @return bool
     */
    public static function is_supported(): bool {
        return function_exists( 'sys_getloadavg' );
    }

    /**
     * Current 1-minute load average, or null when unavailable.
     *
     * @return float|null
     */
    public static function current_load(): ?float {
        if ( ! self::is_supported() ) {
            return null;
        }

        $load = sys_getloadavg();
        if ( ! is_array( $load ) || ! isset( $load[0] ) || ! is_numeric( $load[0] ) ) {
            return null;
        }

        return (float) $load[0];
    }

    /**
     * Whether the user enabled load-aware throttling.
     *
     * @param array<string,mixed> $settings Cache settings.
     * @return bool
     */
    public static function is_enabled( array $settings ): bool {
        return ! empty( $settings['preload_load_throttle_enabled'] );
    }

    /**
     * Configured load ceiling (falls back to the safe default).
     *
     * @param array<string,mixed> $settings Cache settings.
     * @return float
     */
    public static function max_load( array $settings ): float {
        $max = isset( $settings['preload_max_load_average'] )
            ? (float) $settings['preload_max_load_average']
            : self::DEFAULT_MAX_LOAD;

        if ( $max <= 0 ) {
            $max = self::DEFAULT_MAX_LOAD;
        }

        return $max;
    }

    /**
     * Configured delay between batches, clamped to the guard rails.
     *
     * @param array<string,mixed> $settings Cache settings.
     * @param int                 $fallback Delay to use when unset.
     * @return int
     */
    public static function batch_delay( array $settings, int $fallback = self::DEFAULT_BATCH_DELAY ): int {
        $delay = isset( $settings['preload_batch_delay'] )
            ? (int) $settings['preload_batch_delay']
            : $fallback;

        // Clamp any value (configured or fallback) to the guard rails.
        return (int) min( self::MAX_BATCH_DELAY, max( self::MIN_BATCH_DELAY, $delay ) );
    }

    /**
     * Evaluate the throttle decision for the current request.
     *
     * @param array<string,mixed> $settings Cache settings.
     * @return array{enabled:bool,supported:bool,load:?float,max:float,throttle:bool}
     */
    public static function evaluate( array $settings ): array {
        $enabled   = self::is_enabled( $settings );
        $supported = self::is_supported();
        $load      = self::current_load();
        $max       = self::max_load( $settings );

        // Only throttle when the user opted in, the host can report load, and
        // the measured load is above the configured ceiling.
        $throttle = $enabled && $supported && $load !== null && $load > $max;

        return [
            'enabled'   => $enabled,
            'supported' => $supported,
            'load'      => $load,
            'max'       => $max,
            'throttle'  => $throttle,
        ];
    }

    /**
     * Convenience predicate: should the next batch be deferred?
     *
     * @param array<string,mixed> $settings Cache settings.
     * @return bool
     */
    public static function should_throttle( array $settings ): bool {
        return self::evaluate( $settings )['throttle'];
    }
}
