<?php

namespace App\Services;

use App\DTO\DurationResult;
use App\Enums\DurationModifier;
use App\Traits\Invokable;
use DateInterval;
use DatePeriod;
use DateTime;
use DateTimeImmutable;
use ValueError;
use TypeError;
use Illuminate\Contracts\Container\BindingResolutionException;
use Spatie\LaravelData\Exceptions\CannotCreateData;
use Spatie\LaravelData\Exceptions\CannotSetComputedValue;

/** @package App\Services */
class CalculateDuration
{
    const WEEKDAYS = [1, 2, 3, 4, 5];

    /**
     * @param DateTime $start
     * @param DateTime $end
     *
     * @return void
     */
    public function __construct(
        protected DateTimeImmutable $start,
        protected DateTimeImmutable $end
    ) {
    }

    /**
     * @param DurationModifier $durationModifier
     *
     * @return DurationResult
     *
     * @throws ValueError
     * @throws TypeError
     * @throws BindingResolutionException
     * @throws CannotCreateData
     * @throws CannotSetComputedValue
     */
    public function result(DurationModifier $durationModifier = DurationModifier::None): DurationResult
    {
        $days = $this->calculateDuration();
        $weekDays = $this->calculateDuration(true);
        $weeks = $this->calculateDurationWeeks();

        $durationModifierInDays = $durationModifier->durationModiferInDays();

        return new DurationResult(
            days: $days * $durationModifierInDays,
            weeks: $weeks * ($durationModifier === DurationModifier::None ? 1 : $durationModifierInDays * 7),
            weekDays: $weekDays * $durationModifierInDays,
        );
    }

    /**
     * @param string $intervalDuration
     * @param bool $skipWeekends
     *
     * @return int
     */
    protected function calculateDuration(bool $skipWeekends = false): int
    {
        $interval = new DateInterval('P1D');

        $period = new DatePeriod(
            $this->start,
            $interval,
            $this->end->add($interval),
        );

        $intervalCount = 0;
        foreach ($period as $date) {
            if ($skipWeekends && !in_array($date->format('N'), self::WEEKDAYS)) {
                continue;
            }

            $intervalCount++;
        }

        return $intervalCount;
    }

    /**
     * @return int
     */
    protected function calculateDurationWeeks(): int
    {
        return floor($this->start->diff($this->end)->days / 7);
    }
}