<?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\Service\DateUnit;

use BookiFlex\Core\Model\DateUnit\DateUnit;
use BookiFlex\Core\Model\DateUnit\RestrictionRulesConfigurator;
use BookiFlex\Core\Model\DateUnit\RestrictionsChecker;
use BookiFlex\Core\Repository\DateUnitRepository;
use BookiFlex\Core\Repository\RatePlanRepository;
use BookiFlex\Core\Service\DateUnit\DTO\DateUnitsFinderResult;
use BookiFlex\Core\Util\DateTimeUtils;
use BookiFlex\Core\ValueObject\AccommodationSelection;
use BookiFlex\Core\ValueObject\StayPeriod;

defined( 'ABSPATH' ) || exit;

/**
 * Returns DateUnits for selected RatePlan and AccommodationType by range period.
 * If DateUnit related to ChildRatePlan, then it will be inherited from parent RatePlan.
 * Check restrictions for each DateUnit in the period.
 */
class DateUnitFinder
{
    public function __construct(
        private readonly DateUnitRepository $dateUnitRepository,
        private readonly RatePlanRepository $ratePlanRepository,
        private readonly RestrictionsChecker $restrictionsChecker
    ) {
    }

    /**
     * Find DateUnits for the period with inheritance applied
     * @throws \Exception
     */
    public function find(
        StayPeriod $stayPeriod,
        AccommodationSelection $accommodationSelection,
        ?RestrictionRulesConfigurator $restrictionRules = null
    ): DateUnitsFinderResult {
        $dateUnits = $this->findByRange($stayPeriod, $accommodationSelection);
        $ratePlan = $this->ratePlanRepository->findByPK($accommodationSelection->getRatePlanId());

        if (!$ratePlan || count($dateUnits) === 0) {
            return new DateUnitsFinderResult([], true);
        }

        $parentDateUnits = [];
        if ($ratePlan->isChildRatePlan()) {
            $parentDateUnits = $this->findByRange(
                $stayPeriod,
                new AccommodationSelection($accommodationSelection->getAccommodationTypeId(), $ratePlan->getParent()->getId())
            );

            foreach ($dateUnits as $dateStr => $unit) {
                if (isset($parentDateUnits[$dateStr])) {
                    $unit->setParentDateUnit(
                        $parentDateUnits[$dateStr],
                        $ratePlan->getInheritanceRestrictionRules(),
                        $ratePlan->getInheritancePriceRules()
                    );
                }
            }
        }

        $restrictions = [];
        if ($restrictionRules) {
            $this->restrictionsChecker->configRestrictionRules($restrictionRules);
            $restrictions = $this->restrictionsChecker->check(
                $dateUnits,
                $stayPeriod->getCheckInDate(),
                $stayPeriod->getCheckOutDate(),
            );
        }

        $dates = array_map(static function (DateUnit $item) {
            return $item->getDate();
        }, $dateUnits);

        return new DateUnitsFinderResult(
            $dateUnits,
            DateTimeUtils::isPeriodSequenced($dates),
            $restrictions,
            $this->setDateDifferenceBetween($dateUnits, $parentDateUnits)
        );
    }

    /**
     * @return DateUnit[]
     */
    private function findByRange(
        StayPeriod $stayPeriod,
        AccommodationSelection $accommodationSelection,
    ): array {
        $dataUnits = $this->dateUnitRepository->findByRange(
            $stayPeriod->getCheckInDate(),
            $stayPeriod->getCheckOutDate(),
            $accommodationSelection->getRatePlanId(),
            $accommodationSelection->getAccommodationTypeId()
        );

        $units = [];
        foreach ($dataUnits as $unit) {
            $units[$unit->getDate()] = $unit;
        }

        return $units;
    }

    private function setDateDifferenceBetween(array $units, array $parentUnits): array
    {
        return empty($parentUnits)
            ? []
            : array_diff_key($parentUnits, $units);
    }
}
