<?php declare(strict_types = 1);

namespace idoit\Module\SyneticsFlows\Automation\Trigger\TriggerType\Repetition;

use DateInterval;
use DateTimeImmutable;
use Exception;

class MonthRepetition extends Repetition
{
    public const DAY_OF_MONTH = 'day-of-month';

    public const FIRST_WEEKDAY_OF_MONTH = 'first-weekday-of-month';

    public const SECOND_WEEKDAY_OF_MONTH = 'second-weekday-of-month';

    public const THIRD_WEEKDAY_OF_MONTH = 'third-weekday-of-month';

    public const FOURTH_WEEKDAY_OF_MONTH = 'fourth-weekday-of-month';

    public const LAST_WEEKDAY_OF_MONTH = 'last-weekday-of-month';

    /**
     * @param int $period
     * @param string $periodType
     */
    public function __construct(
        int $period,
        protected string $periodType = self::DAY_OF_MONTH,
    )
    {
        parent::__construct($period);
    }

    /**
     * @return string
     */
    public function getPeriodType(): string
    {
        return $this->periodType;
    }

    /**
     * @param DateTimeImmutable $start
     * @param DateTimeImmutable $offset
     *
     * @return int
     */
    protected function getIterationNumber(DateTimeImmutable $start, DateTimeImmutable $offset): int
    {
        if ($offset <= $start) {
            return 0;
        }
        if ($this->periodType !== self::DAY_OF_MONTH) {
            $offset = max($offset->modify('first day of this month'), $offset);
        }

        $diff = $offset->diff($start);
        $count = $diff->y * 12 + $diff->m;
        return (int) floor($count / $this->period);
    }

    /**
     * @param DateTimeImmutable $start
     * @param int $iteration
     *
     * @return DateTimeImmutable[]
     *
     * @throws Exception
     */
    protected function getNextIterationDates(DateTimeImmutable $start, int $iteration): array
    {
        $next = $start->add(new DateInterval('P' . ($iteration * $this->period) . 'M'));
        $modifier = match ($this->periodType) {
            default => null,
            self::FIRST_WEEKDAY_OF_MONTH => 'first',
            self::SECOND_WEEKDAY_OF_MONTH => 'second',
            self::THIRD_WEEKDAY_OF_MONTH => 'third',
            self::FOURTH_WEEKDAY_OF_MONTH => 'fourth',
            self::LAST_WEEKDAY_OF_MONTH => 'last',
        };

        if ($modifier === null) {
            return [$next];
        }

        $weekday = $start->format('l');
        $hour = (int) $start->format('H');
        $minute = (int) $start->format('i');
        $seconds = (int) $start->format('s');
        $candidates = [
            $next->modify($modifier . ' ' . $weekday . ' of last month')->setTime($hour, $minute, $seconds),
            $next->modify($modifier . ' ' . $weekday . ' of this month')->setTime($hour, $minute, $seconds),
            $next->modify($modifier . ' ' . $weekday . ' of next month')->setTime($hour, $minute, $seconds),
        ];

        return array_values(array_filter($candidates, fn (DateTimeImmutable $date) => $this->getIterationNumber($start, $date) === $iteration));
    }
}
