<?php

namespace App\Course\Entity;

use App\Appointment\Entity\Appointment;
use App\Course\Repository\CourseSessionRepository;
use App\Shared\Tenant\TenantOwnedTrait;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Uuid;

/**
 * یک جلسه از دوره — برنامه‌ریزی‌شده، رزروشده، انجام‌شده یا رد‌شده.
 *
 * `params` از پروتکل **کپی** می‌شود: بیمار جلسهٔ سوم را با انرژی ۱۶ انجام داده، و اگر
 * پروتکل فردا عوض شود، پروندهٔ او نباید بگوید ۱۸ بوده.
 */
#[ORM\Entity(repositoryClass: CourseSessionRepository::class)]
#[ORM\Table(name: 'course_sessions')]
#[ORM\UniqueConstraint(name: 'uniq_course_session', columns: ['course_id', 'session_number'])]
#[ORM\UniqueConstraint(name: 'uniq_session_appointment', columns: ['appointment_id'])]
#[ORM\Index(columns: ['entity_type', 'entity_id', 'status'], name: 'idx_sessions_tenant')]
#[ORM\Index(columns: ['course_id', 'session_number'], name: 'idx_sessions_course')]
class CourseSession
{
    use TenantOwnedTrait;

    public const STATUS_PLANNED   = 'planned';
    public const STATUS_BOOKED    = 'booked';
    public const STATUS_COMPLETED = 'completed';
    public const STATUS_SKIPPED   = 'skipped';

    #[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: TreatmentCourse::class, inversedBy: 'sessions')]
    #[ORM\JoinColumn(name: 'course_id', nullable: false, onDelete: 'CASCADE')]
    private TreatmentCourse $course;

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

    /** @var array<string, scalar>|null */
    #[ORM\Column(type: 'json', nullable: true)]
    private ?array $params = null;

    #[ORM\ManyToOne(targetEntity: Appointment::class)]
    #[ORM\JoinColumn(name: 'appointment_id', nullable: true, onDelete: 'SET NULL')]
    private ?Appointment $appointment = null;

    #[ORM\Column(type: 'string', length: 12, options: ['default' => self::STATUS_PLANNED])]
    private string $status = self::STATUS_PLANNED;

    #[ORM\Column(name: 'completed_at', type: 'integer', nullable: true)]
    private ?int $completedAt = null;

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

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

    /** @param array<string, mixed> $params */
    public function __construct(TreatmentCourse $course, int $sessionNumber, array $params = [])
    {
        $this->uuid          = Uuid::v4()->toRfc4122();
        $this->course        = $course;
        $this->sessionNumber = $sessionNumber;
        $this->params        = $params === [] ? null : $params;
        $this->createdAt     = time();
        $this->updatedAt     = time();

        $this->assignTenantPair($course->getEntityType(), $course->getEntityId());
        $course->addSession($this);
    }

    public function getId(): ?int                 { return $this->id; }
    public function getUuid(): string             { return $this->uuid; }
    public function getCourse(): TreatmentCourse  { return $this->course; }
    public function getSessionNumber(): int       { return $this->sessionNumber; }
    public function getParams(): array            { return $this->params ?? []; }
    public function getAppointment(): ?Appointment { return $this->appointment; }
    public function getStatus(): string           { return $this->status; }
    public function getCompletedAt(): ?int        { return $this->completedAt; }

    public function markBooked(Appointment $appointment): self
    {
        $this->appointment = $appointment;
        $this->status      = self::STATUS_BOOKED;

        return $this->touch();
    }

    /** لغو نوبت جلسه را به `planned` برمی‌گرداند؛ بقیهٔ دوره دست‌نخورده می‌ماند. */
    public function unbook(): self
    {
        $this->appointment = null;
        $this->status      = self::STATUS_PLANNED;

        return $this->touch();
    }

    public function markCompleted(?int $at = null): self
    {
        $this->status      = self::STATUS_COMPLETED;
        $this->completedAt = $at ?? time();

        return $this->touch();
    }

    public function markSkipped(): self
    {
        $this->status = self::STATUS_SKIPPED;

        return $this->touch();
    }

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

        return $this;
    }

    /** @return array<string, mixed> */
    public function toArray(): array
    {
        return [
            'uuid'             => $this->uuid,
            'session_number'   => $this->sessionNumber,
            'params'           => (object) ($this->params ?? []),
            'appointment_uuid' => $this->appointment?->getUuid(),
            'slot_start'       => $this->appointment?->getSlotStart(),
            'status'           => $this->status,
            'completed_at'     => $this->completedAt,
        ];
    }
}
