<?php

namespace App\Shared\Time;

/**
 * یک بازهٔ نیم‌باز `[start, end)`.
 *
 * واحدش عمداً تعیین نشده: در تقویم هفتگی «دقیقه از نیمه‌شب» است و بعد از
 * `shiftedBy()` می‌شود timestamp مطلق. جبرِ زیر در هر دو حالت یکسان کار می‌کند، و
 * همین باعث می‌شود تقاطع ساعت شعبه با شیفت منبع و کسر مرخصی، یک کد باشند نه دو تا.
 */
final readonly class TimeInterval
{
    public function __construct(
        public int $start,
        public int $end,
    ) {
        if ($end <= $start) {
            throw new \InvalidArgumentException(sprintf('Interval end (%d) must be after start (%d).', $end, $start));
        }
    }

    /**
     * بازه را از «دقیقه از نیمه‌شب» به timestamp مطلق می‌برد.
     *
     * نامش عمداً `shiftedBy` نیست: جمع ساده، دقیقه را با ثانیه قاطی می‌کرد و بازهٔ
     * هشت‌ساعته را هشت **ثانیه** می‌ساخت — دقیقاً همان باگی که
     * ResourceAvailabilityTest اولین بار گرفت.
     */
    public function minutesToAbsolute(int $midnight): self
    {
        return new self($midnight + $this->start * 60, $midnight + $this->end * 60);
    }

    public function toArray(): array
    {
        return ['start' => $this->start, 'end' => $this->end];
    }

    /**
     * بازه‌های هم‌پوشان یا چسبیده را یکی می‌کند. خروجی مرتب است.
     *
     * @param list<self> $intervals
     * @return list<self>
     */
    public static function mergeAll(array $intervals): array
    {
        if ($intervals === []) {
            return [];
        }

        usort($intervals, static fn (self $a, self $b): int => $a->start <=> $b->start);

        $merged = [array_shift($intervals)];

        foreach ($intervals as $next) {
            $last = $merged[count($merged) - 1];

            if ($next->start <= $last->end) {
                // چسبیده هم ادغام می‌شود: [9,13) و [13,17) یعنی [9,17)، نه دو بازه.
                $merged[count($merged) - 1] = new self($last->start, max($last->end, $next->end));
                continue;
            }

            $merged[] = $next;
        }

        return $merged;
    }

    /**
     * تقاطع دو مجموعه بازه.
     *
     * @param list<self> $left
     * @param list<self> $right
     * @return list<self>
     */
    public static function intersectAll(array $left, array $right): array
    {
        $out = [];

        foreach (self::mergeAll($left) as $a) {
            foreach (self::mergeAll($right) as $b) {
                $start = max($a->start, $b->start);
                $end   = min($a->end, $b->end);

                if ($end > $start) {
                    $out[] = new self($start, $end);
                }
            }
        }

        return self::mergeAll($out);
    }

    /**
     * `$from` منهای `$blocks`. بازهٔ نیم‌روزه فقط همان تکه را می‌بُرد و بقیهٔ روز
     * سرِ جایش می‌ماند.
     *
     * @param list<self> $from
     * @param list<self> $blocks
     * @return list<self>
     */
    public static function subtractAll(array $from, array $blocks): array
    {
        $result = self::mergeAll($from);

        foreach (self::mergeAll($blocks) as $block) {
            $next = [];

            foreach ($result as $interval) {
                if ($block->end <= $interval->start || $block->start >= $interval->end) {
                    $next[] = $interval;   // بی‌تداخل
                    continue;
                }

                if ($block->start > $interval->start) {
                    $next[] = new self($interval->start, $block->start);
                }

                if ($block->end < $interval->end) {
                    $next[] = new self($block->end, $interval->end);
                }
            }

            $result = $next;
        }

        return $result;
    }
}
