<?php

declare(strict_types=1);

namespace Autogedal\FeedMonitor\Model\Firebear;

/**
 * Average minutes between two runs of a cron expression with a regular schedule (every N minutes / hours, daily,
 * on some weekdays). Null when the days of the month or the months are restricted ("13 10 1 1 1", "* * 30 2 *"):
 * such jobs are not checked for "not running".
 */
class CronInterval
{
    public function getMinutes(string $expression): ?int
    {
        $fields = preg_split('/\s+/', trim($expression)) ?: [];
        if (count($fields) !== 5) {
            return null;
        }
        [$minute, $hour, $day, $month, $weekday] = $fields;
        if ($day !== '*' || $month !== '*') {
            return null;
        }

        $runsPerWeek = $this->count($minute, 0, 59) * $this->count($hour, 0, 23)
            * ($weekday === '*' ? 7 : count(array_unique(array_map(
                static fn (int $value): int => $value % 7, // 0 and 7 are both Sunday
                $this->values($weekday, 0, 7)
            ))));

        return $runsPerWeek > 0 ? max(1, intdiv(7 * 1440, $runsPerWeek)) : null;
    }

    private function count(string $field, int $min, int $max): int
    {
        return count($this->values($field, $min, $max));
    }

    /**
     * Values of one field: "*", "*\/n", "a", "a-b", "a-b/n", lists of these.
     *
     * @return int[]
     */
    private function values(string $field, int $min, int $max): array
    {
        $values = [];
        foreach (explode(',', $field) as $part) {
            if (!preg_match('/^(\*|\d+(?:-\d+)?)(?:\/(\d+))?$/', $part, $match)) {
                return [];
            }
            $step = isset($match[2]) ? max(1, (int)$match[2]) : 1;
            [$from, $to] = $match[1] === '*'
                ? [$min, $max]
                : array_map('intval', array_pad(explode('-', $match[1]), 2, explode('-', $match[1])[0]));
            for ($value = max($min, $from); $value <= min($max, $to); $value += $step) {
                $values[$value] = $value;
            }
        }

        return array_values($values);
    }
}
