<?php

declare(strict_types=1);

namespace PhpWA\Integration;

use InvalidArgumentException;
use PhpWA\Exception\RunCancelledException;
use PhpWA\Runtime\RunCheckpoint;
use PhpWA\Runtime\RunGuardInterface;

/**
 * Stops an obsolete auth/cron worker when the application's channel generation
 * changes. The optional heartbeat receives only a secret-free checkpoint.
 */
final class GenerationRunGuard implements RunGuardInterface
{
    /** @var int */ private $expectedGeneration;
    /** @var callable */ private $generationReader;
    /** @var callable|null */ private $heartbeat;

    /**
     * @param callable():int $generationReader
     * @param callable(RunCheckpoint):void|null $heartbeat
     */
    public function __construct(int $expectedGeneration, callable $generationReader, ?callable $heartbeat = null)
    {
        if ($expectedGeneration < 1) {
            throw new InvalidArgumentException('Expected run generation must be positive.');
        }
        $this->expectedGeneration = $expectedGeneration;
        $this->generationReader = $generationReader;
        $this->heartbeat = $heartbeat;
    }

    public function check(RunCheckpoint $checkpoint): void
    {
        $current = ($this->generationReader)();
        if (!is_int($current) || $current < 1) {
            throw new InvalidArgumentException('Run generation reader must return a positive integer.');
        }
        if ($current !== $this->expectedGeneration) {
            throw new RunCancelledException('PhpWA worker was superseded by a newer application generation.');
        }
        if ($this->heartbeat !== null) {
            ($this->heartbeat)($checkpoint);
        }
    }
}
