<?php

declare(strict_types=1);

namespace Drupal\flowdrop_trigger\Service;

use Drupal\Core\State\StateInterface;
use Psr\Log\LoggerInterface;

/**
 * Handles schedule jitter for cron triggers.
 *
 * Jitter spreads cron trigger execution across a configurable time window to
 * prevent thundering-herd problems when many workflows are scheduled to run at
 * the same time (e.g. all at midnight). On the first cron invocation after a
 * trigger is due, a random delay is chosen and stored; the trigger is held back
 * until a subsequent cron run arrives after that delay has elapsed.
 *
 * State key schema: flowdrop_trigger.jitter.{configId}
 *
 * @internal This class is not part of the public API and may change without notice.
 */
class JitterHandler {

  public function __construct(
    private readonly StateInterface $state,
    private readonly LoggerInterface $logger,
  ) {}

  /**
   * Determines whether a cron trigger should be delayed by jitter.
   *
   * On first call (no stored delay): picks a random offset within
   * [minSeconds, maxSeconds], persists the target fire time, and returns
   * TRUE (hold back). On subsequent cron runs: returns TRUE while still
   * inside the window, or FALSE once the delay has elapsed (clearing state).
   *
   * @param string $configId
   *   The trigger configuration ID.
   * @param int $minSeconds
   *   Minimum jitter delay in seconds (inclusive lower bound).
   * @param int $maxSeconds
   *   Maximum jitter delay in seconds (inclusive upper bound, >= minSeconds).
   *
   * @return bool
   *   TRUE if the trigger should wait, FALSE if it should fire immediately.
   */
  public function shouldDelay(string $configId, int $minSeconds, int $maxSeconds): bool {
    if ($maxSeconds <= 0) {
      return FALSE;
    }

    $min = max(0, $minSeconds);
    $max = max($min, $maxSeconds);

    $stateKey = 'flowdrop_trigger.jitter.' . $configId;
    $fireAt = $this->state->get($stateKey);
    $now = time();

    if ($fireAt === NULL) {
      // First invocation: schedule a random delay and defer execution.
      $delay = random_int($min, $max);
      $this->state->set($stateKey, $now + $delay);
      $this->logger->info(
        'Jitter applied to trigger @id: holding for @delay s (fires after @time)',
        [
          '@id' => $configId,
          '@delay' => $delay,
          '@time' => date('H:i:s', $now + $delay),
        ]
      );
      return TRUE;
    }

    if ($now >= $fireAt) {
      // Delay has elapsed: clear state and let the trigger proceed.
      $this->state->delete($stateKey);
      return FALSE;
    }

    // Still within the jitter window.
    return TRUE;
  }

}
