<?php

declare(strict_types=1);

namespace Plugin\tnt_stabila_shop\src\Helper;

/**
 * Pure decision helper for time-of-day alert muting ("quiet hours").
 *
 * Operators can define a nightly maintenance/off-hours window during which
 * admin error alerts (and their optional mail copy) are suppressed to avoid
 * alert fatigue — errors are still recorded, only the notification is withheld.
 * The window is two hour-of-day integers [0..23]: alerts are muted from
 * $fromHour (inclusive) up to $untilHour (exclusive), wrapping across midnight
 * when $fromHour > $untilHour (e.g. 22 -> 6).
 *
 * An empty window ($fromHour === $untilHour) or any out-of-range/unset hour
 * leaves the feature disabled, preserving the historic "always alert"
 * behaviour, so enabling quiet hours is fully opt-in.
 */
class AlertScheduleDecider
{
    /** Lowest valid hour of day. */
    public const HOUR_MIN = 0;
    /** Highest valid hour of day. */
    public const HOUR_MAX = 23;

    /**
     * Whether a usable quiet-hours window is configured.
     *
     * @param int|null $fromHour  Window start hour (inclusive); null/out-of-range disables.
     * @param int|null $untilHour Window end hour (exclusive); null/out-of-range disables.
     * @return bool True only when both hours are valid and the window is non-empty.
     */
    public static function isConfigured(?int $fromHour, ?int $untilHour): bool
    {
        if (self::isValidHour($fromHour) === false || self::isValidHour($untilHour) === false) {
            return false;
        }

        return $fromHour !== $untilHour;
    }

    /**
     * Whether the given hour of day falls inside the quiet-hours window.
     *
     * The window is half-open [$fromHour, $untilHour) and wraps across midnight
     * when $fromHour > $untilHour. An empty window ($fromHour === $untilHour)
     * contains no hour.
     *
     * @param int $hourOfDay Current hour of day (normalised into 0..23).
     * @param int $fromHour  Window start hour (inclusive).
     * @param int $untilHour Window end hour (exclusive).
     * @return bool True when the hour is muted by the window.
     */
    public static function isWithinQuietHours(int $hourOfDay, int $fromHour, int $untilHour): bool
    {
        if ($fromHour === $untilHour) {
            return false;
        }
        $hour = self::normaliseHour($hourOfDay);
        if ($fromHour < $untilHour) {
            return $hour >= $fromHour && $hour < $untilHour;
        }

        // Wrap-around window (e.g. 22 -> 6): muted late tonight or early tomorrow.
        return $hour >= $fromHour || $hour < $untilHour;
    }

    /**
     * Decide whether an alert may be dispatched at the given hour.
     *
     * @param int      $hourOfDay Current hour of day (0..23).
     * @param int|null $fromHour  Quiet-window start hour; null/out-of-range = disabled.
     * @param int|null $untilHour Quiet-window end hour; null/out-of-range = disabled.
     * @return bool False only when quiet hours are configured and the hour is inside them.
     */
    public static function shouldAlert(int $hourOfDay, ?int $fromHour, ?int $untilHour): bool
    {
        if (self::isConfigured($fromHour, $untilHour) === false) {
            return true;
        }

        return self::isWithinQuietHours($hourOfDay, (int)$fromHour, (int)$untilHour) === false;
    }

    private static function isValidHour(?int $hour): bool
    {
        return $hour !== null && $hour >= self::HOUR_MIN && $hour <= self::HOUR_MAX;
    }

    private static function normaliseHour(int $hour): int
    {
        $hour %= 24;
        if ($hour < 0) {
            $hour += 24;
        }

        return $hour;
    }
}
