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

use BookiFlex\Core\Exception\LogicException;
use BookiFlex\Core\Model\AccommodationType\Scheme\AllocationScheme;
use BookiFlex\Core\Model\DateUnit\DateUnit;
use BookiFlex\Core\Model\DateUnit\Exception\PriceNotSetByDateException;
use BookiFlex\Core\Util\DateTimeUtils;

defined( 'ABSPATH' ) || exit;

/**
 * Price calculation orchestrator
 * Responsible for:
 * - Input data validation
 * - Calculation strategy selection
 * - Data preparation for calculator
 */
class PriceCalculationOrchestrator
{
    private PriceCalculatorInterface $calculator;
    private bool $isNightStrategy;

    /**
     * @param PriceCalculatorInterface|null $calculator Calculation strategy (default StandardPriceCalculator)
     * @param bool $isNightStrategy If true, last DateUnit excluded from calculation
     */
    public function __construct(
        ?PriceCalculatorInterface $calculator = null,
        bool $isNightStrategy = true
    ) {
        $this->calculator = $calculator ?? new Strategy\StandardPriceCalculator();
        $this->isNightStrategy = $isNightStrategy;
    }

    /**
     * Set calculation strategy
     */
    public function setCalculator(PriceCalculatorInterface $calculator): void
    {
        $this->calculator = $calculator;
    }

    /**
     * Get current strategy
     */
    public function getCalculator(): PriceCalculatorInterface
    {
        return $this->calculator;
    }

    /**
     * Calculate price
     *
     * @param AllocationScheme $allocationScheme Allocation scheme
     * @param DateUnit[] $dateUnits Array of DateUnit
     * @return CalculatedPriceResult
     * @throws PriceNotSetByDateException
     * @throws LogicException
     */
    public function calculate(AllocationScheme $allocationScheme, array $dateUnits): CalculatedPriceResult
    {
        // Validation
        $this->validateDateUnits($dateUnits);

        // Sort by date
        $sortedUnits = $this->sortDateUnits($dateUnits);

        // Apply nights strategy (exclude last day)
        if ($this->isNightStrategy && count($sortedUnits) > 0) {
            array_pop($sortedUnits);
        }

        // Delegate calculation to selected strategy
        return $this->calculator->calculate($sortedUnits, $allocationScheme);
    }

    /**
     * Validate DateUnit array
     * @throws PriceNotSetByDateException
     * @throws LogicException
     */
    private function validateDateUnits(array $dateUnits): void
    {
        if (empty($dateUnits)) {
            throw new PriceNotSetByDateException('Date units are not set.', 400);
        }

        // Check date sequence
        $dates = array_map(fn(DateUnit $unit) => $unit->getDate(), $dateUnits);

        if (!DateTimeUtils::isPeriodSequenced($dates)) {
            throw new LogicException('Date units are not sequenced.', 500);
        }
    }

    /**
     * Sort DateUnit by date
     */
    private function sortDateUnits(array $dateUnits): array
    {
        $sorted = $dateUnits;

        usort($sorted, function(DateUnit $a, DateUnit $b) {
            return strcmp($a->getDate(), $b->getDate());
        });

        return array_values($sorted);
    }
}
