<?php
declare(strict_types=1);

/** Pure decision model: never performs filesystem, process, HTTP or DB actions. */
function raceRecoveryDecision(array $journal, array $observed): array
{
    $lane = (string)($journal['lane'] ?? '');
    $marker = (string)($journal['marker'] ?? '');
    if (!in_array($lane, ['A', 'B', 'C'], true)
        || preg_match('/^CDXEMRACE[A-F0-9]{12}$/D', $marker) !== 1
        || !in_array((string)($journal['phase'] ?? ''),
            ['stage_started', 'stage_failed', 'workers_starting', 'staged',
                'healthy', 'audited', 'workers_stopped', 'archive_started'], true)
        || ((int)($journal['menu_id'] ?? 0) > 0
            && ($journal['phase'] ?? '') !== 'audited'
            && ($journal['phase'] ?? '') !== 'workers_stopped'
            && ($journal['phase'] ?? '') !== 'archive_started')
        || ((int)($journal['code_id'] ?? 0) > 0
            && ($journal['phase'] ?? '') !== 'audited'
            && ($journal['phase'] ?? '') !== 'workers_stopped'
            && ($journal['phase'] ?? '') !== 'archive_started')) {
        return ['allowed' => false, 'reason' => 'phase_or_business_data_not_cleared'];
    }
    $nonce = strtolower(substr($marker, strlen('CDXEMRACE')));
    $rootPrefix = '/tmp/cdx-em-race-' . strtolower($lane) . '-' . $nonce;
    $barrier = '/tmp/cdx-em-race-barrier-' . $nonce;
    if (($journal['barrier_dir'] ?? null) !== $barrier
        || !is_array($journal['worker_roots'] ?? null)
        || !is_array($journal['worker_pids'] ?? null)
        || !is_array($journal['worker_start_ticks'] ?? null)
        || !is_array($journal['worker_ports'] ?? null)
        || !is_array($journal['worker_launch'] ?? null)) {
        return ['allowed' => false, 'reason' => 'manifest_shape_invalid'];
    }
    $stopPids = [];
    $archiveRoots = [];
    $ignoredUnownedPorts = [];
    foreach ([1, 2] as $side) {
        if (($journal['worker_roots'][$side] ?? null) !== $rootPrefix . '-' . $side
            || !isset($observed[$side]) || !is_array($observed[$side])) {
            return ['allowed' => false, 'reason' => 'root_or_observation_invalid'];
        }
        $item = $observed[$side];
        $pid = (int)($journal['worker_pids'][$side] ?? 0);
        $port = (int)($journal['worker_ports'][$side] ?? 0);
        $launch = (string)($journal['worker_launch'][$side] ?? '');
        if (!in_array($launch, ['not_started', 'starting', 'pid_recorded'], true)
            || $pid < 0 || $port < 0) {
            return ['allowed' => false, 'reason' => 'root_or_launch_state_invalid'];
        }
        if (($item['root_exists'] ?? false) !== true) {
            if ($pid !== 0 || $launch !== 'not_started') {
                return ['allowed' => false, 'reason' => 'missing_root_with_possible_worker'];
            }
            continue;
        }
        if ($pid > 0) {
            if ($launch !== 'pid_recorded') {
                return ['allowed' => false, 'reason' => 'pid_not_durably_recorded'];
            }
            $birth = (string)($journal['worker_start_ticks'][$side] ?? '');
            if ($birth === '' || !ctype_digit($birth)) {
                // A sidecar PID without durable generation is not enough to kill.
                return ['allowed' => false, 'reason' => 'pid_generation_not_recorded'];
            }
            if (($item['pid_present'] ?? false) === true) {
                if (($item['pid_start_ticks'] ?? null) !== $birth) {
                    return ['allowed' => false, 'reason' => 'pid_generation_changed'];
                }
                if (in_array(($item['pid_state'] ?? ''), ['Z', 'X', 'x'], true)) {
                    if (($item['port_listening'] ?? false) === true) {
                        return ['allowed' => false, 'reason' => 'exited_pid_port_reoccupied'];
                    }
                } elseif (($item['pid_owned'] ?? false) !== true
                    || ($item['pid_socket_owner'] ?? false) !== true) {
                    return ['allowed' => false, 'reason' => 'pid_not_proven_owned'];
                } else {
                    $stopPids[] = $pid;
                }
            } elseif (($item['port_listening'] ?? false) === true) {
                return ['allowed' => false, 'reason' => 'recorded_pid_gone_port_reoccupied'];
            }
        } elseif ($launch === 'starting') {
            // A child may have started while PID capture failed: do not archive.
            return ['allowed' => false, 'reason' => 'launch_pid_unknown'];
        } elseif (($item['port_listening'] ?? false) === true) {
            // A conflict before we started side 2 is not our process.
            $ignoredUnownedPorts[] = $port;
        }
        $archiveRoots[] = $journal['worker_roots'][$side];
    }
    return ['allowed' => true, 'reason' => 'owned_private_recovery_only',
        'stop_pids' => $stopPids, 'archive_roots' => $archiveRoots,
        'archive_barrier' => $barrier,
        'ignored_unowned_ports' => $ignoredUnownedPorts,
        'original_service_required' => false];
}
