<?php

namespace App\Course\Entity;

use App\ClinicService\Entity\ServiceItem;
use App\Course\Repository\CourseProtocolRepository;
use App\Shared\Tenant\TenantOwnedTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Uuid;

/**
 * پروتکل دوره — «لیزر فول‌بادی: ۸ جلسه، ۲۱/۲۸/۴۵ روز».
 *
 * سه فاصله سه معنای متفاوت دارند: `min` زودترین زمانی که از نظر درمانی مجاز است،
 * `ideal` بهترین، و `max` جایی که دیرتر از آن اثر دوره افت می‌کند. برنامه‌ریز به
 * **نزدیک‌ترین به ایده‌آل** می‌رسد، نه اولین وقت خالی.
 */
#[ORM\Entity(repositoryClass: CourseProtocolRepository::class)]
#[ORM\Table(name: 'course_protocols')]
#[ORM\UniqueConstraint(name: 'uniq_protocol_service', columns: ['service_item_id'])]
#[ORM\Index(columns: ['entity_type', 'entity_id', 'active'], name: 'idx_protocols_tenant')]
class CourseProtocol
{
    use TenantOwnedTrait;

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private ?int $id = null;

    #[ORM\Column(type: 'string', length: 36, unique: true)]
    private string $uuid;

    #[ORM\ManyToOne(targetEntity: ServiceItem::class)]
    #[ORM\JoinColumn(name: 'service_item_id', nullable: false, onDelete: 'CASCADE')]
    private ServiceItem $serviceItem;

    #[ORM\Column(name: 'session_count', type: 'smallint')]
    private int $sessionCount;

    #[ORM\Column(name: 'min_days', type: 'smallint')]
    private int $minDays;

    #[ORM\Column(name: 'ideal_days', type: 'smallint')]
    private int $idealDays;

    #[ORM\Column(name: 'max_days', type: 'smallint')]
    private int $maxDays;

    #[ORM\Column(name: 'prefer_same_resource', type: 'boolean', options: ['default' => true])]
    private bool $preferSameResource = true;

    #[ORM\Column(type: 'boolean', options: ['default' => true])]
    private bool $active = true;

    /** @var Collection<int, CourseProtocolStep> */
    #[ORM\OneToMany(targetEntity: CourseProtocolStep::class, mappedBy: 'protocol', cascade: ['persist', 'remove'], orphanRemoval: true)]
    #[ORM\OrderBy(['sessionNumber' => 'ASC'])]
    private Collection $steps;

    #[ORM\Column(name: 'created_at', type: 'integer')]
    private int $createdAt;

    #[ORM\Column(name: 'updated_at', type: 'integer')]
    private int $updatedAt;

    public function __construct(ServiceItem $serviceItem, int $sessionCount, int $minDays, int $idealDays, int $maxDays)
    {
        $this->uuid        = Uuid::v4()->toRfc4122();
        $this->serviceItem = $serviceItem;
        $this->steps       = new ArrayCollection();
        $this->createdAt   = time();
        $this->updatedAt   = time();

        $this->setShape($sessionCount, $minDays, $idealDays, $maxDays);

        $section = $serviceItem->getSection();
        $this->assignTenantPair($section->getEntityType(), $section->getEntityId());
    }

    /**
     * @throws \InvalidArgumentException وقتی فاصله‌ها ناسازگارند
     */
    public function setShape(int $sessionCount, int $minDays, int $idealDays, int $maxDays): self
    {
        // دورهٔ یک‌جلسه‌ای همان نوبت تکی است و به دوره نیازی ندارد.
        if ($sessionCount < 2) {
            throw new \InvalidArgumentException('A course needs at least two sessions.');
        }

        if (!($minDays <= $idealDays && $idealDays <= $maxDays)) {
            throw new \InvalidArgumentException('Course spacing must satisfy min <= ideal <= max.');
        }

        $this->sessionCount = $sessionCount;
        $this->minDays      = $minDays;
        $this->idealDays    = $idealDays;
        $this->maxDays      = $maxDays;

        return $this->touch();
    }

    public function getId(): ?int                { return $this->id; }
    public function getUuid(): string            { return $this->uuid; }
    public function getServiceItem(): ServiceItem { return $this->serviceItem; }
    public function getSessionCount(): int       { return $this->sessionCount; }
    public function getMinDays(): int            { return $this->minDays; }
    public function getIdealDays(): int          { return $this->idealDays; }
    public function getMaxDays(): int            { return $this->maxDays; }
    public function prefersSameResource(): bool  { return $this->preferSameResource; }
    public function isActive(): bool             { return $this->active; }

    /** @return Collection<int, CourseProtocolStep> */
    public function getSteps(): Collection { return $this->steps; }

    public function setPreferSameResource(bool $v): self { $this->preferSameResource = $v; return $this->touch(); }
    public function setActive(bool $v): self             { $this->active = $v; return $this->touch(); }

    public function addStep(CourseProtocolStep $step): self
    {
        if (!$this->steps->contains($step)) {
            $this->steps->add($step);
        }

        return $this;
    }

    /** پارامترهای جلسهٔ n — آرایهٔ خالی یعنی این جلسه پارامتری ندارد. */
    public function paramsFor(int $sessionNumber): array
    {
        foreach ($this->steps as $step) {
            if ($step->getSessionNumber() === $sessionNumber) {
                return $step->getParams();
            }
        }

        return [];
    }

    public function overrideDurationFor(int $sessionNumber): ?int
    {
        foreach ($this->steps as $step) {
            if ($step->getSessionNumber() === $sessionNumber) {
                return $step->getOverrideDurationMinutes();
            }
        }

        return null;
    }

    private function touch(): self
    {
        $this->updatedAt = time();

        return $this;
    }

    /** @return array<string, mixed> */
    public function toArray(): array
    {
        return [
            'uuid'                 => $this->uuid,
            'service_uuid'         => $this->serviceItem->getUuid(),
            'service_name'         => $this->serviceItem->getName(),
            'session_count'        => $this->sessionCount,
            'min_days'             => $this->minDays,
            'ideal_days'           => $this->idealDays,
            'max_days'             => $this->maxDays,
            'prefer_same_resource' => $this->preferSameResource,
            'active'               => $this->active,
            'steps'                => array_values(array_map(
                static fn (CourseProtocolStep $s): array => $s->toArray(),
                $this->steps->toArray(),
            )),
            'created_at'           => $this->createdAt,
        ];
    }
}
