<?php
/**
 * BookiFlex Core
 *
 * @package    BookiFlex
 * @subpackage Core
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Core\Util\DateRange;

use BookiFlex\Core\Exception\InvalidArgumentException;
use DateTimeImmutable;
use DateTimeInterface;

defined( 'ABSPATH' ) || exit;

/**
 * The DateRangeCreator class is responsible for creating date ranges between two given dates.
 * It provides a method create that takes start and end dates as strings
 * and optionally takes the minimum and maximum dates of period that is already set (and we want to exclude it).
 *
 * The class ensures that the dates are valid and creates date ranges accordingly.
 */
class DateRangeCreator
{
    /**
     * @param string $from start date in Y-m-d format
     * @param string $to end date in Y-m-d format
     * @param string|null $currentMinDate min date of already set unit (in Y-m-d format)
     * @param string|null $currentMaxDate max date of already set unit (in Y-m-d format)
     * @return DateRange[]
     * @throws InvalidArgumentException
     */
    public function create(
        string $from,
        string $to,
        ?string $currentMinDate = null,
        ?string $currentMaxDate = null
    ): array {
        $dateFrom = DateTimeImmutable::createFromFormat('Y-m-d', $from)->setTime(0, 0);
        $dateTo   = DateTimeImmutable::createFromFormat('Y-m-d', $to)->setTime(0, 0);

        if ($dateFrom > $dateTo) {
            throw new InvalidArgumentException(
                'Set valid dates. Date from {dateFrom}; Date to {dateTo}.',
                400,
                null,
                [
                    'dateFrom' => $from,
                    'dateTo'   => $to,
                ]
            );
        }

        // min date that already set
        // if not set then set it as DateFrom
        $minDate = $currentMinDate
            ? (DateTimeImmutable::createFromFormat('Y-m-d', $currentMinDate))->setTime(0, 0)->modify('-1 day')
            : clone $dateFrom;

        // max date that already set
        // if not set then set it manually
        $maxDate = $currentMaxDate
            ? (DateTimeImmutable::createFromFormat('Y-m-d', $currentMaxDate))->setTime(0, 0)->modify('+1 day')
            : clone $dateTo;

        $ranges = $this->makeDateRanges($dateFrom, $dateTo, $minDate, $maxDate);

        bookiflex()?->getLogger()?->info('DateRangeCreator::create', [
            'from'           => $from,
            'to'             => $to,
            'currentMin'     => $currentMinDate,
            'currentMax'     => $currentMaxDate,
            'minDate'        => $minDate->format('Y-m-d'),
            'maxDate'        => $maxDate->format('Y-m-d'),
            'rangesCount'    => count($ranges),
            'ranges'         => array_map(
                fn($r) => $r->getDateFrom('Y-m-d') . ' → ' . $r->getDateTo('Y-m-d'),
                $ranges
            ),
        ]);

        return $ranges;
    }

    /**
     *     currentMinDate               currentMaxDate
     * ----------|<------------------------->|-------
     *
     * @param DateTimeImmutable $dateFrom
     * @param DateTimeImmutable $dateTo
     * @param DateTimeImmutable|null $minDate
     * @param DateTimeImmutable|null $maxDate
     * @return array
     * @throws InvalidArgumentException
     */
    private function makeDateRanges(
        DateTimeImmutable $dateFrom,
        DateTimeImmutable $dateTo,
        DateTimeImmutable $minDate = null,
        DateTimeImmutable $maxDate = null
    ): array {
        $ranges = [];

        if ($minDate >= $dateFrom && $dateTo->diff($maxDate)->days !== 0) {
            $period = new DateRange(
                $dateFrom,
                $minDate,
                DateRange::DAY_STRATEGY
            );
            $ranges[] = $period;
        }

        if ($maxDate <= $dateTo && $dateFrom->diff($minDate)->days !== 0) {
            $period = new DateRange(
                $maxDate,
                $dateTo,
                DateRange::DAY_STRATEGY
            );
            $ranges[] = $period;
        }

        if ($dateFrom->diff($minDate)->days === 0 && $dateTo->diff($maxDate)->days === 0) {
            $period = new DateRange(
                $dateFrom,
                $dateTo,
                DateRange::DAY_STRATEGY
            );
            $ranges[] = $period;
        }

        return $ranges;
    }
}
