$fallbackHours */ public function __construct( private readonly int $dayMarginMinutes, private readonly int $hourMarginMinutes, private readonly array $fallbackHours, ) {} /** Окно, открытое в момент $now, или null (тихие часы / срок прошёл / состояние не напоминает). */ public function current(EventState $state, User $user, Due $due, CarbonImmutable $now): ?Window { $activeStart = $user->activeStart($now); $activeEnd = $user->activeEnd($now); if ($now->lt($activeStart) || $now->gte($activeEnd)) { return null; } return match ($state) { EventState::Preparing, EventState::Overdue => new Window( $activeStart, $activeEnd, true, $activeEnd->subMinutes($this->dayMarginMinutes), ), EventState::Today => $this->hourly($due, $now, $activeEnd), default => null, }; } private function hourly(Due $due, CarbonImmutable $now, CarbonImmutable $activeEnd): ?Window { $start = $now->startOfHour(); $end = $start->addHour()->min($activeEnd); $truncated = false; if ($due->moment->gt($start) && $due->moment->lt($end)) { $end = $due->moment; $truncated = true; } if ($now->gte($end)) { return null; } $fallbackAllowed = $truncated || in_array((int) $start->format('G'), $this->fallbackHours, true); return new Window($start, $end, $fallbackAllowed, $end->subMinutes($this->hourMarginMinutes)); } }