<?php

namespace App\Appointment\Availability\Repository;

use App\Appointment\Availability\Entity\ResourceOccupancy;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @extends ServiceEntityRepository<ResourceOccupancy>
 */
class ResourceOccupancyRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, ResourceOccupancy::class);
    }

    /**
     * اشغال‌های همهٔ منابع در یک بازه — **یک کوئری برای کل جستجو**، نه یکی per منبع
     * یا per روز. موتور جستجو این را یک بار می‌گیرد و در حافظه می‌برد.
     *
     * @param int[] $resourceIds
     * @return array<int, list<array{start: int, end: int}>> شناسهٔ منبع => بازه‌ها
     */
    public function busyByResource(array $resourceIds, int $from, int $to): array
    {
        if ($resourceIds === []) {
            return [];
        }

        $rows = $this->createQueryBuilder('o')
            ->select('IDENTITY(o.resource) AS resource_id, o.startsAt AS starts_at, o.endsAt AS ends_at')
            ->where('o.resource IN (:ids)')
            ->andWhere('o.startsAt < :to')
            ->andWhere('o.endsAt > :from')
            // ردیف آزادشده تاریخچه است، نه اشغال؛ اگر شمرده شود، زمانِ لغوشده هرگز
            // دوباره پیشنهاد نمی‌شود.
            ->andWhere('o.status IN (:blocking)')
            ->setParameter('blocking', ResourceOccupancy::BLOCKING_STATUSES)
            ->setParameter('ids', $resourceIds)
            ->setParameter('from', $from)
            ->setParameter('to', $to)
            ->orderBy('o.startsAt', 'ASC')
            ->getQuery()
            ->getArrayResult();

        $byResource = [];
        foreach ($rows as $row) {
            $byResource[(int) $row['resource_id']][] = [
                'start' => (int) $row['starts_at'],
                'end'   => (int) $row['ends_at'],
            ];
        }

        return $byResource;
    }

    /** @return ResourceOccupancy[] */
    /**
     * آیا در این بازه نوبت یا رزرو موقتی روی این منبع هست؟
     *
     * فقط ردیف‌هایی که به نوبت یا hold وصل‌اند؛ مسدودسازی دستیِ دیگر مانع نیست —
     * دو بازهٔ سرویس دستگاه می‌توانند هم‌پوشان باشند و آن مشکلی نیست.
     */
    public function hasAppointmentInRange(int $resourceId, int $from, int $to): bool
    {
        return (int) $this->createQueryBuilder('o')
            ->select('COUNT(o.id)')
            ->where('IDENTITY(o.resource) = :resource')
            ->andWhere('o.appointmentId IS NOT NULL OR o.holdId IS NOT NULL')
            ->andWhere('o.status IN (:blocking)')
            ->andWhere('o.startsAt < :to')
            ->andWhere('o.endsAt > :from')
            ->setParameter('resource', $resourceId)
            ->setParameter('blocking', ResourceOccupancy::BLOCKING_STATUSES)
            ->setParameter('from', $from)
            ->setParameter('to', $to)
            ->getQuery()
            ->getSingleScalarResult() > 0;
    }

    /**
     * چند نوبتِ **آینده** روی این منبع نشسته است؟
     *
     * غیرفعال‌کردن منبع نوبت‌های ثبت‌شده را لغو نمی‌کند — فقط از جستجوی وقتِ بعدی
     * حذفش می‌کند. پس این عدد هشدار است نه مانع: اپراتور باید بداند چند بیمار روی
     * منبعی نوبت دارند که دارد خاموش می‌شود.
     */
    public function countUpcomingAppointments(int $resourceId, ?int $now = null): int
    {
        return (int) $this->createQueryBuilder('o')
            ->select('COUNT(DISTINCT o.appointmentId)')
            ->where('IDENTITY(o.resource) = :resource')
            ->andWhere('o.appointmentId IS NOT NULL')
            ->andWhere('o.status IN (:blocking)')
            ->andWhere('o.endsAt > :now')
            ->setParameter('resource', $resourceId)
            ->setParameter('blocking', ResourceOccupancy::BLOCKING_STATUSES)
            ->setParameter('now', $now ?? time())
            ->getQuery()
            ->getSingleScalarResult();
    }

    /**
     * مسدودسازی‌های دستیِ یک منبع در یک بازه.
     *
     * فقط ردیف‌های بدون نوبت: بقیه اشغالِ رزرو واقعی‌اند و از صفحهٔ منابع مدیریت
     * نمی‌شوند — حذفشان از آنجا یعنی نوبت بیمار بی‌صدا منبعش را از دست بدهد.
     *
     * @return ResourceOccupancy[]
     */
    public function findManualBlocks(int $resourceId, int $from, int $to): array
    {
        return $this->createQueryBuilder('o')
            ->where('IDENTITY(o.resource) = :resource')
            ->andWhere('o.appointmentId IS NULL')
            ->andWhere('o.holdId IS NULL')
            ->andWhere('o.status IN (:blocking)')
            ->andWhere('o.startsAt < :to')
            ->andWhere('o.endsAt > :from')
            ->setParameter('resource', $resourceId)
            ->setParameter('blocking', ResourceOccupancy::BLOCKING_STATUSES)
            ->setParameter('from', $from)
            ->setParameter('to', $to)
            ->orderBy('o.startsAt', 'ASC')
            ->getQuery()
            ->getResult();
    }

    public function findForAppointment(int $appointmentId): array
    {
        return $this->findBy(['appointmentId' => $appointmentId]);
    }
}
