<?php

declare(strict_types=1);

namespace App\Module\Absence\Domain\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use App\Module\Absence\Domain\Enum\AbsenceType;
use App\Module\Absence\Infrastructure\ApiPlatform\State\AbsenceBalanceProvider;
use App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsenceBalanceRepository;
use App\Shared\Domain\Contract\BlamableInterface;
use App\Shared\Domain\Contract\TimestampableInterface;
use App\Shared\Domain\Contract\UserInterface;
use App\Shared\Domain\Trait\TimestampableBlamableTrait;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;

/**
 * Per-employee, per-type leave balance for a given reference period.
 */
#[ApiResource(
    operations: [
        new GetCollection(
            paginationEnabled: false,
            security: "is_granted('ROLE_USER')",
            provider: AbsenceBalanceProvider::class,
        ),
        new Get(
            security: "is_granted('ROLE_USER')",
            provider: AbsenceBalanceProvider::class,
        ),
        new Patch(security: "is_granted('ROLE_ADMIN')"),
    ],
    normalizationContext: ['groups' => ['absence_balance:read']],
    denormalizationContext: ['groups' => ['absence_balance:write']],
)]
#[ORM\Entity(repositoryClass: DoctrineAbsenceBalanceRepository::class)]
#[ORM\Table(name: 'absence_balance')]
#[ORM\UniqueConstraint(name: 'uniq_absence_balance_user_type_period', columns: ['user_id', 'type', 'period'])]
class AbsenceBalance implements TimestampableInterface, BlamableInterface
{
    use TimestampableBlamableTrait;

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

    #[ORM\ManyToOne(targetEntity: UserInterface::class)]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    #[Groups(['absence_balance:read'])]
    private ?UserInterface $user = null;

    #[ORM\Column(type: Types::STRING, length: 32, enumType: AbsenceType::class)]
    #[Groups(['absence_balance:read'])]
    private AbsenceType $type;

    /** Reference period, e.g. "2025-2026" for paid leave or "2025" for yearly. */
    #[ORM\Column(length: 16)]
    #[Groups(['absence_balance:read'])]
    private ?string $period = null;

    /** Days acquired during the *previous* reference period (Congés N-1): fully available to take. */
    #[ORM\Column(type: Types::FLOAT)]
    #[Groups(['absence_balance:read', 'absence_balance:write'])]
    private float $acquired = 0.0;

    /** Days being accrued during the *current* reference period (Congés N): "en cours d'acquisition". */
    #[ORM\Column(type: Types::FLOAT)]
    #[Groups(['absence_balance:read', 'absence_balance:write'])]
    private float $acquiring = 0.0;

    #[ORM\Column(type: Types::FLOAT)]
    #[Groups(['absence_balance:read', 'absence_balance:write'])]
    private float $taken = 0.0;

    /** Sum of days in PENDING requests, for information. */
    #[ORM\Column(type: Types::FLOAT)]
    #[Groups(['absence_balance:read'])]
    private float $pending = 0.0;

    /** Last month (format YYYY-MM) for which the monthly accrual was applied. */
    #[ORM\Column(length: 7, nullable: true)]
    private ?string $lastAccruedMonth = null;

    /** Total entitlement for the period, both finalized (N-1) and in-progress (N). */
    #[Groups(['absence_balance:read'])]
    public function getAcquiredTotal(): float
    {
        return $this->acquired + $this->acquiring;
    }

    /**
     * Days the employee can still take: in this organisation the days being
     * accrued (N) are posable too, so they count towards what is available.
     */
    #[Groups(['absence_balance:read'])]
    public function getAvailable(): float
    {
        return $this->acquired + $this->acquiring - $this->taken;
    }

    #[Groups(['absence_balance:read'])]
    public function getLabel(): string
    {
        return $this->type->label();
    }

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

    public function getUser(): ?UserInterface
    {
        return $this->user;
    }

    public function setUser(?UserInterface $user): static
    {
        $this->user = $user;

        return $this;
    }

    public function getType(): AbsenceType
    {
        return $this->type;
    }

    public function setType(AbsenceType $type): static
    {
        $this->type = $type;

        return $this;
    }

    public function getPeriod(): ?string
    {
        return $this->period;
    }

    public function setPeriod(string $period): static
    {
        $this->period = $period;

        return $this;
    }

    public function getAcquired(): float
    {
        return $this->acquired;
    }

    public function setAcquired(float $acquired): static
    {
        $this->acquired = $acquired;

        return $this;
    }

    public function getAcquiring(): float
    {
        return $this->acquiring;
    }

    public function setAcquiring(float $acquiring): static
    {
        $this->acquiring = $acquiring;

        return $this;
    }

    public function getTaken(): float
    {
        return $this->taken;
    }

    public function setTaken(float $taken): static
    {
        $this->taken = $taken;

        return $this;
    }

    public function getPending(): float
    {
        return $this->pending;
    }

    public function setPending(float $pending): static
    {
        $this->pending = $pending;

        return $this;
    }

    public function getLastAccruedMonth(): ?string
    {
        return $this->lastAccruedMonth;
    }

    public function setLastAccruedMonth(?string $lastAccruedMonth): static
    {
        $this->lastAccruedMonth = $lastAccruedMonth;

        return $this;
    }
}
