<?php
declare(strict_types=1);

// Pure policy: a PID directory alone is not proof of a running worker.
function raceParseProcStat(string $line): array
{
    $close = strrpos($line, ') ');
    if ($close === false) {
        throw new RuntimeException('Process stat format uncertain');
    }
    $fields = preg_split('/\s+/', trim(substr($line, $close + 2)));
    // After comm, index 0 is field 3 (state), index 19 is field 22 (starttime).
    if (!is_array($fields) || count($fields) < 20
        || !preg_match('/^[RSDZTtXxKWPI]$/D', (string)$fields[0])
        || !ctype_digit((string)$fields[19])) {
        throw new RuntimeException('Process stat identity uncertain');
    }
    return ['state' => (string)$fields[0],
        'start_ticks' => (string)$fields[19]];
}

function raceReadProcStat(int $pid): ?array
{
    if ($pid <= 0) throw new RuntimeException('Process PID invalid');
    $path = '/proc/' . $pid;
    for ($attempt = 0; $attempt < 10; $attempt++) {
        clearstatcache(true, $path);
        if (!is_dir($path)) return null;
        $line = @file_get_contents($path . '/stat');
        if (is_string($line) && $line !== '') return raceParseProcStat($line);
        usleep(20000);
    }
    clearstatcache(true, $path);
    if (!is_dir($path)) return null;
    throw new RuntimeException('Process state unreadable while PID exists');
}

function raceWorkerExitDecision(?array $observed, string $expectedStartTicks,
    bool $portListening): string
{
    if ($expectedStartTicks === '' || !ctype_digit($expectedStartTicks)) {
        return 'identity_unknown';
    }
    if ($observed === null) {
        return $portListening ? 'port_reoccupied' : 'exited';
    }
    if (($observed['start_ticks'] ?? null) !== $expectedStartTicks) {
        return 'pid_reused';
    }
    if (in_array(($observed['state'] ?? ''), ['Z', 'X', 'x'], true)) {
        return $portListening ? 'port_reoccupied' : 'exited';
    }
    return 'active';
}
