<?php

namespace App\Appointment\Plan\Entity;

use App\Appointment\Plan\Repository\SegmentTemplateRepository;
use App\ClinicService\Entity\ServiceItem;
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;

/**
 * یک بخش از نوبت — بند ۷ مستند.
 *
 * نوبت یک تکه زمان پیوسته نیست. «مالیدن کرم بی‌حسی (۵ دقیقه، اتاق+اپراتور)» و
 * «انتظار اثر کرم (۳۰ دقیقه، فقط اتاق)» دو بخش‌اند؛ با مدل تک‌بازه‌ای اپراتور ۶۰
 * دقیقه قفل می‌شود در حالی که ۳۰ دقیقه کار می‌کند — نصف ظرفیت هدر می‌رود.
 */
#[ORM\Entity(repositoryClass: SegmentTemplateRepository::class)]
#[ORM\Table(name: 'segment_templates')]
#[ORM\Index(columns: ['entity_type', 'entity_id'], name: 'idx_segment_tenant')]
#[ORM\Index(columns: ['service_id', 'sequence'], name: 'idx_segment_service_seq')]
class SegmentTemplate
{
    use TenantOwnedTrait;

    /** مدت ثابت است. */
    public const DURATION_FIXED = 'fixed';

    /**
     * مدت از آیتم‌های انتخاب‌شده می‌آید ({@see \App\ClinicService\Service\DurationCalculator}).
     * بخش «خود لیزر» با دو ناحیه طولانی‌تر می‌شود، ولی «انتظار اثر کرم» نه.
     */
    public const DURATION_FROM_ITEMS = 'items';

    public const DURATION_SOURCES = [self::DURATION_FIXED, self::DURATION_FROM_ITEMS];

    /** سقف مجموع مدت یک نوبت — حفاظت از جستجوی وقت در تسک ۰۶. */
    public const MAX_TOTAL_MINUTES = 480;

    /**
     * سقف تعداد — همان حفاظت، از سمت دیگر.
     *
     * موتور دسترس‌پذیری برای هر بخش × هر نیازمندی یک بار ترکیب منابع را می‌سنجد؛ صد
     * بخشِ ده‌نیازمندی یک جستجوی وقت را از پا درمی‌آورد. عددها سخاوتمندانه‌اند: هیچ
     * سرویس واقعی‌ای به آن‌ها نمی‌رسد، ولی ورودی اشتباه یا اسکریپت خراب می‌رسد.
     */
    public const MAX_SEGMENTS = 20;

    public const MAX_REQUIREMENTS_PER_SEGMENT = 10;

    #[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_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
    private ServiceItem $service;

    #[ORM\Column(type: 'smallint')]
    private int $sequence;

    #[ORM\Column(type: 'string', length: 150)]
    private string $name;

    #[ORM\Column(name: 'duration_source', type: 'string', length: 10, options: ['default' => self::DURATION_FIXED])]
    private string $durationSource = self::DURATION_FIXED;

    #[ORM\Column(name: 'duration_minutes', type: 'smallint', options: ['default' => 0])]
    private int $durationMinutes = 0;

    /** بیمار در این بخش حاضر است؟ «انتظار در خانه» نمونهٔ خلافش است. */
    #[ORM\Column(name: 'patient_present', type: 'boolean', options: ['default' => true])]
    private bool $patientPresent = true;

    /**
     * با انتخاب چند آیتم، این بخش **یک بار** می‌آید نه چند بار.
     * «آماده‌سازی» یک بار انجام می‌شود؛ «خود لیزر» به‌ازای هر ناحیه.
     */
    #[ORM\Column(type: 'boolean', options: ['default' => false])]
    private bool $mergeable = false;

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

    /** @var Collection<int, SegmentRequirement> */
    #[ORM\OneToMany(targetEntity: SegmentRequirement::class, mappedBy: 'segment', cascade: ['persist', 'remove'], orphanRemoval: true)]
    private Collection $requirements;

    public function __construct(ServiceItem $service, int $sequence, string $name)
    {
        $this->uuid         = Uuid::v4()->toRfc4122();
        $this->service      = $service;
        $this->sequence     = $sequence;
        $this->name         = $name;
        $this->createdAt    = time();
        $this->requirements = new ArrayCollection();

        $this->assignTenantPair($service->getSection()->getEntityType(), $service->getSection()->getEntityId());
    }

    public function getId(): ?int             { return $this->id; }
    public function getUuid(): string         { return $this->uuid; }
    public function getService(): ServiceItem { return $this->service; }
    public function getSequence(): int        { return $this->sequence; }
    public function getName(): string         { return $this->name; }
    public function getDurationSource(): string { return $this->durationSource; }
    public function getDurationMinutes(): int { return $this->durationMinutes; }
    public function isPatientPresent(): bool  { return $this->patientPresent; }
    public function isMergeable(): bool       { return $this->mergeable; }

    /** @return Collection<int, SegmentRequirement> */
    public function getRequirements(): Collection { return $this->requirements; }

    public function setPatientPresent(bool $v): self { $this->patientPresent = $v; return $this; }
    public function setMergeable(bool $v): self      { $this->mergeable = $v; return $this; }

    /** @throws \InvalidArgumentException روی منبع مدت ناشناخته یا مدت ناممکن */
    public function setDuration(string $source, int $minutes): self
    {
        if (!in_array($source, self::DURATION_SOURCES, true)) {
            throw new \InvalidArgumentException(sprintf('Unknown duration source "%s".', $source));
        }

        // بخشی که مدتش از آیتم‌ها می‌آید، عدد ثابت لازم ندارد؛ بخش ثابت حتماً دارد.
        if ($source === self::DURATION_FIXED && $minutes <= 0) {
            throw new \InvalidArgumentException('A fixed segment needs a positive duration.');
        }

        if ($minutes > self::MAX_TOTAL_MINUTES) {
            throw new \InvalidArgumentException('Segment duration exceeds the daily cap.');
        }

        $this->durationSource  = $source;
        $this->durationMinutes = max(0, $minutes);

        return $this;
    }

    public function toArray(): array
    {
        return [
            'uuid'             => $this->uuid,
            'service_uuid'     => $this->service->getUuid(),
            'sequence'         => $this->sequence,
            'name'             => $this->name,
            'duration_source'  => $this->durationSource,
            'duration_minutes' => $this->durationMinutes,
            'patient_present'  => $this->patientPresent,
            'mergeable'        => $this->mergeable,
            'requirements'     => array_map(
                static fn (SegmentRequirement $r): array => $r->toArray(),
                $this->requirements->toArray(),
            ),
        ];
    }
}
