<?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\Api\Transformer;

use BookiFlex\Core\Exception\MoneyCalculationException;
use BookiFlex\Core\Model\AccommodationType\AccommodationPlacementTypeInterface;
use BookiFlex\Core\Model\AccommodationType\Accommodation\AccommodationType;
use BookiFlex\Core\Model\DateUnit\DateUnit;
use BookiFlex\Core\Service\DateUnit\DTO\CalendarData;
use BookiFlex\Core\Util\AccommodationTypeScenario;
use Brick\Money\Currency;
use Brick\Money\Exception\UnknownCurrencyException;

defined( 'ABSPATH' ) || exit;

/**
 * Transforms CalendarData DTO into API response format
 */
class CalendarTransformer
{
    private static ?int $currencyFractionDigits = null;

    private const CHILD_PLACEMENT_TYPES = [
        AccommodationPlacementTypeInterface::CHILD_ON_MAIN_PLACE,
        AccommodationPlacementTypeInterface::CHILD_ON_EXTRA_PLACE,
        AccommodationPlacementTypeInterface::CHILD_WITHOUT_PLACE,
    ];

    /**
     * Transform CalendarData into API response array
     *
     * @throws MoneyCalculationException
     */
    public static function transform(CalendarData $data, string $currency, bool $childrenEnabled): array
    {
        self::initializeCurrency($currency);

        $ratePlan = $data->getRatePlan();
        $inheritRestrictionRules = $ratePlan->getInheritanceRestrictionRules();

        $result = [
            'ratePlan' => [
                'id'           => $ratePlan->getId(),
                'name'         => $ratePlan->getName(),
                'active'       => $ratePlan->isActive(),
                'isChildren'   => $ratePlan->isChildRatePlan(),
                'inheritRules' => $inheritRestrictionRules ? [
                    'stopSale'      => $inheritRestrictionRules->inheritStopSale(),
                    'cta'           => $inheritRestrictionRules->inheritCloseToArrival(),
                    'ctd'           => $inheritRestrictionRules->inheritCloseToDeparture(),
                    'minLos'        => $inheritRestrictionRules->inheritMinLengthOfStay(),
                    'maxLos'        => $inheritRestrictionRules->inheritMaxLengthOfStay(),
                    'minLosArrival' => $inheritRestrictionRules->inheritMinLengthOfStayArrival(),
                    'maxLosArrival' => $inheritRestrictionRules->inheritMaxLengthOfStayArrival(),
                    'minAdvBooking' => $inheritRestrictionRules->inheritMinAdvBooking(),
                    'maxAdvBooking' => $inheritRestrictionRules->inheritMaxAdvBooking(),
                ] : null,
            ],
            'accommodationTypes' => [],
            'dateUnits'          => [],
        ];

        // Transform accommodation types
        foreach ($data->getAccommodationTypesData() as $accommodationTypeData) {
            /** @var AccommodationType $accommodationType */
            $accommodationType = $accommodationTypeData['accommodationType'];
            $isSync = $accommodationTypeData['isSync'];

            $result['accommodationTypes'][] = [
                'id'         => $accommodationType->getId(),
                'name'       => $accommodationType->getName(),
                'capacities' => self::transformCapacity($accommodationType, $childrenEnabled),
                'active'     => $accommodationType->isActive(),
                'isSync'     => $isSync,
            ];
        }

        // Transform date units
        foreach ($data->getDateUnitsGrouped() as $accommodationTypeId => $dateUnits) {
            /** @var DateUnit $dateUnit */
            foreach ($dateUnits as $dateUnit) {
                $day = $dateUnit->getDate();
                $result['dateUnits'][$accommodationTypeId][$day] = [
                    'date'              => $day,
                    'price'             => self::preparePrices($dateUnit, $childrenEnabled),
                    'accommodationType' => $accommodationTypeId,
                    'stopSale'          => $dateUnit->isStopSale(),
                    'cta'               => $dateUnit->isCloseToArrival(),
                    'ctd'               => $dateUnit->isCloseToDeparture(),
                    'minLos'            => $dateUnit->getMinLengthOfStay(),
                    'maxLos'            => $dateUnit->getMaxLengthOfStay(),
                    'minLosArrival'     => $dateUnit->getMinLengthOfStayArrival(),
                    'maxLosArrival'     => $dateUnit->getMaxLengthOfStayArrival(),
                    'minAdvBooking'     => $dateUnit->getMinAdvanceBooking(),
                    'maxAdvBooking'     => $dateUnit->getMaxAdvanceBooking(),
                ];
            }
        }

        return $result;
    }

    /**
     * Transform accommodation type capacity rules into API format
     */
    private static function transformCapacity(AccommodationType $accommodationType, bool $childrenEnabled): array
    {
        $placementRules = $accommodationType->getRules()->toArray();

        $result = [];
        foreach ($placementRules as $scenario => $variants) {
            if (!$childrenEnabled && in_array($scenario, self::CHILD_PLACEMENT_TYPES, true)) {
                continue;
            }
            foreach ($variants as $variant => $active) {
                if (!$active) {
                    continue;
                }
                $result[] = [
                    'name'     => sprintf(
                        '%s %s',
                        str_replace('__', 'x', $variant),
                        AccommodationTypeScenario::getName($scenario)
                    ),
                    'scenario' => $scenario,
                    'variant'  => $variant,
                ];
            }
        }

        return $result;
    }

    /**
     * Prepare price data for API response
     */
    private static function preparePrices(DateUnit $dateUnit, bool $childrenEnabled): array
    {
        $price = $dateUnit->getPrice();
        if (!$price) {
            return [];
        }

        $efficientPrices = $price->getEffectivePrices();

        $formated = [];
        foreach ($efficientPrices as $placementType => $variants) {
            if (!$childrenEnabled && in_array($placementType, self::CHILD_PLACEMENT_TYPES, true)) {
                continue;
            }
            foreach ($variants as $variant => $amount) {
                $formated[$placementType][$variant] = $amount?->format(self::$currencyFractionDigits);
            }
        }

        return [
            'inherited'  => $price->isInherited(),
            'overridden' => $price->isOverridden(),
            'value'      => $formated,
        ];
    }

    /**
     * Initialize currency fraction digits
     *
     * @throws MoneyCalculationException
     */
    private static function initializeCurrency(string $currencyCode): void
    {
        if (self::$currencyFractionDigits !== null) {
            return;
        }

        try {
            $currency = Currency::of($currencyCode);
        } catch (UnknownCurrencyException $e) {
            throw new MoneyCalculationException(
                sprintf(
                    'Failed to calculate money: %s',
                    $e->getMessage()
                ),
                0,
                $e,
                ['error' => $e->getMessage()]
            );
        }

        self::$currencyFractionDigits = $currency->getDefaultFractionDigits();
    }
}
