<?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\RatePlan;

use BookiFlex\Core\Exception\LogicException;
use BookiFlex\Core\Model\AccommodationType\Accommodation\AccommodationType;
use BookiFlex\Core\Model\CancellationPolicy\CancellationPolicy;
use BookiFlex\Core\Model\Identifiable;
use BookiFlex\Core\Model\PaymentType\PaymentType;
use DateTimeInterface;

defined( 'ABSPATH' ) || exit;

class RatePlan
{
    use Identifiable;

    /** @var CancellationPolicy[] */
    private array $cancellationPolicy;
    private readonly array $meta;

    /**
     * @param string $name
     * @param string $description
     * @param string $content
     * @param string $code
     * @param bool $active
     * @param array $paymentTypes
     * @param array $accommodationTypes
     * @param BoardType $boardType
     * @param CancellationPolicy|null $cancellationPolicy
     * @param RatePlan|null $parent
     * @param InheritancePriceRules|null $inheritancePriceRules
     * @param InheritanceRestrictionRules|null $inheritanceRestrictionRules
     * @throws LogicException
     */
    public function __construct(
        private readonly string $name,
        private readonly string $description,
        private readonly string $content,
        private readonly string $code,
        private readonly bool $active,
        private readonly array $paymentTypes,
        private readonly array $accommodationTypes,
        private readonly BoardType $boardType,
        CancellationPolicy $cancellationPolicy = null,
        private readonly ?RatePlan $parent = null,
        private readonly ?InheritancePriceRules $inheritancePriceRules = null,
        private readonly ?InheritanceRestrictionRules $inheritanceRestrictionRules = null,
        array $meta = []
    ) {
        $this->meta = $meta;
        if ($cancellationPolicy) {
            $this->cancellationPolicy[] = $cancellationPolicy;
        }

        if ($this->parent && $this->parent->isChildRatePlan()) {
            throw new LogicException('Only single-level inheritance is supported.');
        }
    }

    public function isHiddenFromFrontend(): bool
    {
        return $this->meta['hiddenFromFrontend'] ?? false;
    }

    public function getParent(): ?RatePlan
    {
        return $this->parent;
    }

    public function isChildRatePlan(): bool
    {
        return $this->parent !== null;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getDescription(): string
    {
        return $this->description;
    }

    public function getContent(): string
    {
        return $this->content;
    }

    public function isActive(): bool
    {
        return $this->active;
    }

    public function getBoardType(): BoardType
    {
        return $this->boardType;
    }

    public function getCode(): string
    {
        return $this->code;
    }

    /**
     * @return PaymentType[]
     */
    public function getPaymentTypes(): array
    {
        return $this->paymentTypes;
    }

    /**
     * @param DateTimeInterface $dateTime
     * @return PaymentType[]
     * @throws \Exception
     */
    public function getAvailablePaymentTypes(DateTimeInterface $dateTime): array
    {
        return array_values(array_filter($this->paymentTypes, function (PaymentType $paymentType) use ($dateTime) {
            return $paymentType->isActive() && $paymentType->isAvailableOn($dateTime);
        }));
    }

    /**
     * @return CancellationPolicy|null
     */
    public function getCancellationPolicy(): ?CancellationPolicy
    {
        if (empty($this->cancellationPolicy)) {
            return null;
        }

        return $this->cancellationPolicy[0];
    }

    /**
     * @param int $id
     * @return bool
     */
    public function supportAccommodationType(int $id): bool
    {
        return in_array($id, array_map(fn (AccommodationType $type) => $type->getId(), $this->accommodationTypes));
    }

    /**
     * @return AccommodationType[]
     */
    public function getAccommodationTypes(): array
    {
        return $this->accommodationTypes;
    }

    public function getInheritancePriceRules(): ?InheritancePriceRules
    {
        if (!$this->isChildRatePlan()) {
            return null;
        }

        if ($this->inheritancePriceRules !== null) {
            return $this->inheritancePriceRules;
        }

        return new InheritancePriceRules([]);
    }

    public function getInheritanceRestrictionRules(): ?InheritanceRestrictionRules
    {
        if (!$this->isChildRatePlan()) {
            return null;
        }

        if ($this->inheritanceRestrictionRules !== null) {
            return $this->inheritanceRestrictionRules;
        }

        return new InheritanceRestrictionRules();
    }

    /**
     * IDs of extra services included in this rate plan scoped to an accommodation type.
     * Returns [] in Free edition (meta never populated) or when no extras are linked.
     *
     * Each linked extra must explicitly list the accommodation types it applies to.
     * An empty `accommodationTypeIds` list means the link does not apply to any type
     * (the admin must explicitly select target types in the rate plan settings).
     * Calling with `$accommodationTypeId = null` returns ids without scope filtering
     * (used for "is anything included at all" checks).
     *
     * @return int[]
     */
    public function getIncludedExtraIds(?int $accommodationTypeId = null): array
    {
        $entries = $this->meta['extras'] ?? [];

        return array_values(array_map(
            fn(array $e) => $e['serviceId'],
            array_filter($entries, function (array $e) use ($accommodationTypeId): bool {
                if ($accommodationTypeId === null) {
                    return true;
                }
                if (empty($e['accommodationTypeIds'])) {
                    return false;
                }
                return in_array($accommodationTypeId, $e['accommodationTypeIds'], true);
            })
        ));
    }

    public function hasIncludedExtras(): bool
    {
        return !empty($this->meta['extras']);
    }
}
