<?php

namespace App\Appointment\Booking\Entity;

use App\Appointment\Availability\Entity\ResourceOccupancy;
use App\Resource\Entity\ClinicResource;
use Doctrine\ORM\Mapping as ORM;

/**
 * تضمین یکتاییِ اشغال — **در سطح دیتابیس، نه در کد**.
 *
 * قانون سوم جمع‌بندی مستند: «جلوگیری از رزرو تکراری کار دیتابیس است، نه کار کد».
 * MariaDB قید `EXCLUDE` بازه‌ای ندارد، پس هر بازهٔ اشغال به «سطل»های ثابت پنج‌دقیقه‌ای
 * شکسته می‌شود و کلید یکتای `(resource_id, bucket_at, seat)` تداخل را غیرممکن می‌کند.
 *
 * `seat` ظرفیت را بیان می‌کند: اتاق سه‌تخته سه صندلی دارد (۰،۱،۲) و چهارمین رزرو
 * هم‌زمان جایی برای نشستن پیدا نمی‌کند. بدون `seat`، ظرفیت را باید کد می‌شمرد و
 * دقیقاً همان‌جا مسابقه شکل می‌گرفت.
 */
#[ORM\Entity]
#[ORM\Table(name: 'resource_occupancy_buckets')]
#[ORM\UniqueConstraint(name: 'uniq_bucket_resource_seat', columns: ['resource_id', 'bucket_at', 'seat'])]
#[ORM\Index(columns: ['occupancy_id'], name: 'idx_bucket_occupancy')]
class OccupancyBucket
{
    /** دانهٔ زمانی. پنج دقیقه: ریزتر یعنی ردیف بیشتر، درشت‌تر یعنی رزرو دقیق ناممکن. */
    public const BUCKET_SECONDS = 300;

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

    #[ORM\ManyToOne(targetEntity: ClinicResource::class)]
    #[ORM\JoinColumn(name: 'resource_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
    private ClinicResource $resource;

    #[ORM\ManyToOne(targetEntity: ResourceOccupancy::class)]
    #[ORM\JoinColumn(name: 'occupancy_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
    private ResourceOccupancy $occupancy;

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

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

    public function __construct(ResourceOccupancy $occupancy, int $bucketAt, int $seat)
    {
        $this->occupancy = $occupancy;
        $this->resource  = $occupancy->getResource();
        $this->bucketAt  = $bucketAt;
        $this->seat      = $seat;
    }

    public function getId(): ?int       { return $this->id; }
    public function getBucketAt(): int  { return $this->bucketAt; }
    public function getSeat(): int      { return $this->seat; }

    /**
     * سطل‌هایی که یک بازه لمس می‌کند.
     *
     * بازه نیم‌باز است، پس نوبتی که دقیقاً سرِ ساعت تمام می‌شود سطل بعدی را نمی‌گیرد.
     *
     * @return list<int>
     */
    public static function bucketsFor(int $start, int $end): array
    {
        $first = intdiv($start, self::BUCKET_SECONDS) * self::BUCKET_SECONDS;
        $buckets = [];

        for ($at = $first; $at < $end; $at += self::BUCKET_SECONDS) {
            $buckets[] = $at;
        }

        return $buckets;
    }
}
