<?php

/*
 * @author TRYDEA - 2024
 */

namespace Webapp\Core\Entity;

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\ExistsFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Core\Serializer\Filter\GroupFilter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
use Shared\Authentication\CustomFilters\DeletedFilter;
use Shared\Authentication\Entity\IdentifiedEntity;
use Shared\Authentication\Entity\Site;
use Shared\Authentication\Entity\User;
use Shared\RightManagement\Annotation\AdvancedRight;
use Shared\RightManagement\Traits\HasOwnerEntity;
use Shared\Utils\ArrayCollectionUtils;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Webapp\Core\ApiOperation\RestoreObjectOperation;
use Webapp\Core\Dto\Protocol\ProtocolInputPostDto;
use Webapp\Core\Dto\Protocol\ProtocolInputPutDto;
use Webapp\Core\Entity\Attachment\ProtocolAttachment;

/**
 * @ApiResource(
 *     collectionOperations={
 *         "get"={"security"="is_granted('ROLE_PLATFORM_MANAGER')"},
 *         "post"={
 *              "security_post_denormalize"="is_granted('PROTOCOL_POST', object)",
 *              "input"=ProtocolInputPostDto::class,
 *          },
 *     },
 *     itemOperations={
 *          "get"={"security"="is_granted('ROLE_PLATFORM_MANAGER')"},
 *          "put"={
 *              "security"="object.getExperiment() === null && is_granted('ROLE_PLATFORM_MANAGER', object.getSite())",
 *              "input"=ProtocolInputPutDto::class,
 *          },
 *          "patch"={
 *              "security"="is_granted('ROLE_PLATFORM_MANAGER')",
 *              "denormalization_context"={"groups"={"edit"}}
 *          },
 *          "delete"={
 *              "security"="object.getExperiment() === null && is_granted('ROLE_PLATFORM_MANAGER', object.getSite())"
 *          },
 *          "restore"={
 *              "controller"=RestoreObjectOperation::class,
 *              "method"="PATCH",
 *              "path"="/protocols/{id}/restore",
 *              "security"="is_granted('ROLE_SITE_ADMIN')",
 *              "read"=false,
 *              "validate"=false,
 *              "openapi_context"={
 *                  "summary": "Restore deleted protocol",
 *                  "description": "Remove the deleted state"
 *              },
 *          }
 *     }
 * )
 *
 * @ApiFilter(SearchFilter::class, properties={"experiment": "exact", "site": "exact", "name": "exact"})
 * @ApiFilter(ExistsFilter::class, properties={"deletedAt"})
 * @ApiFilter(GroupFilter::class, arguments={"whitelist"={"design_explorer_view", "protocol_full_view", "platform_full_view", "admin_explorer_view", "protocol_synthesis"}})
 * @ApiFilter(DeletedFilter::class)
 *
 * @AdvancedRight(classIdentifier="webapp_protocol", ownerField="owner", parentFields={"experiment"}, siteAttribute="site")
 *
 * @Gedmo\SoftDeleteable()
 *
 * @ORM\Entity()
 *
 * @ORM\Table(name="protocol", schema="webapp")
 */
class Protocol extends IdentifiedEntity
{
    use HasOwnerEntity;

    use SoftDeleteableEntity;

    public const DEFAULT_PREFIX_FROM_CSV = 'p-';
    public const DEFAULT_AIM_FROM_CSV = '';

    /**
     * @ORM\Column(type="string")
     *
     * @Groups({"design_explorer_view", "platform_full_view", "protocol_full_view", "admin_explorer_view", "admin_explorer_view", "edit", "platform_synthesis", "protocol_synthesis"})
     *
     * @Assert\NotBlank
     */
    private string $name = '';

    /**
     * @ORM\Column(type="text")
     *
     * @Groups({"platform_full_view", "edit", "protocol_synthesis", "protocol_full_view"})
     */
    private string $aim = '';

    /**
     * @ORM\Column(type="string", nullable=true)
     *
     * @Groups({"platform_full_view", "edit", "protocol_synthesis", "protocol_full_view"})
     */
    private ?string $comment = null;

    /**
     * @Gedmo\Timestampable(on="create")
     *
     * @ORM\Column(type="datetime")
     *
     * @Groups({"platform_full_view", "protocol_synthesis", "protocol_full_view"})
     */
    private \DateTime $created;

    /**
     * @var Collection<int, Factor>
     *
     * @ORM\OneToMany(targetEntity="Webapp\Core\Entity\Factor", mappedBy="protocol", cascade={"persist", "remove"}, orphanRemoval=true)
     *
     * @Groups({"design_explorer_view", "platform_full_view", "protocol_synthesis", "protocol_full_view"})
     */
    private Collection $factors;

    /**
     * @Groups({"protocol_synthesis"})
     *
     * @ORM\ManyToOne(targetEntity="Shared\Authentication\Entity\Site", inversedBy="protocols")
     */
    private ?Site $site = null;

    /**
     * @var Collection<int, Treatment>
     *
     * @ORM\OneToMany(targetEntity="Webapp\Core\Entity\Treatment", mappedBy="protocol", cascade={"persist", "remove"}, orphanRemoval=true)
     *
     * @Groups({"platform_full_view", "protocol_synthesis", "protocol_full_view"})
     */
    private Collection $treatments;

    /**
     * @var ?User the owner of the entity
     *
     * @ORM\ManyToOne(targetEntity="Shared\Authentication\Entity\User")
     *
     * @Groups({"design_explorer_view", "platform_full_view", "protocol_synthesis", "protocol_full_view"})
     */
    private ?User $owner = null;

    /**
     * @ORM\OneToOne(targetEntity="Webapp\Core\Entity\Experiment", inversedBy="protocol")
     */
    private ?Experiment $experiment = null;

    /**
     * @ORM\ManyToOne(targetEntity="Webapp\Core\Entity\Algorithm", cascade={"persist"})
     *
     * @Groups({"platform_full_view", "platform_synthesis", "protocol_synthesis", "protocol_full_view"})
     */
    private ?Algorithm $algorithm = null;

    /**
     * @var Collection<int, ProtocolAttachment>
     *
     * @ORM\OneToMany(targetEntity="Webapp\Core\Entity\Attachment\ProtocolAttachment", mappedBy="protocol", cascade={"persist", "remove"})
     */
    private Collection $protocolAttachments;

    public function __construct()
    {
        $this->factors = new ArrayCollection();
        $this->treatments = new ArrayCollection();
        $this->protocolAttachments = new ArrayCollection();
        $this->created = new \DateTime();
    }

    /**
     * @Groups({"platform_full_view"})
     *
     * @psalm-mutation-free
     */
    public function getId(): ?int
    {
        return parent::getId();
    }

    /**
     * @psalm-mutation-free
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @return $this
     */
    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getAim(): string
    {
        return $this->aim;
    }

    /**
     * @return $this
     */
    public function setAim(string $aim): self
    {
        $this->aim = $aim;

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getComment(): ?string
    {
        return $this->comment;
    }

    /**
     * @return $this
     */
    public function setComment(?string $comment): self
    {
        $this->comment = $comment;

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getCreated(): \DateTime
    {
        return $this->created;
    }

    /**
     * @return $this
     */
    public function setCreated(\DateTime $created): self
    {
        $this->created = $created;

        return $this;
    }

    /**
     * @return Collection<int, Factor>
     *
     * @psalm-mutation-free
     */
    public function getFactors(): Collection
    {
        return $this->factors;
    }

    /**
     * @param iterable<int, Factor> $factors
     *
     * @return $this
     */
    public function setFactors(iterable $factors): self
    {
        ArrayCollectionUtils::update($this->factors, $factors, function (Factor $factor) {
            $factor->setProtocol($this);
        });

        return $this;
    }

    /**
     * @return $this
     */
    public function addFactors(Factor $factor): self
    {
        if (!$this->factors->contains($factor)) {
            $this->factors->add($factor);
            $factor->setProtocol($this);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function removeFactors(Factor $factor): self
    {
        if ($this->factors->contains($factor)) {
            $this->factors->removeElement($factor);
            $factor->setProtocol(null);
        }

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getSite(): ?Site
    {
        return $this->site;
    }

    /**
     * @return $this
     */
    public function setSite(?Site $site): self
    {
        $this->site = $site;
        $this->experiment = null;

        return $this;
    }

    /**
     * @return Collection<int, Treatment>
     *
     * @psalm-mutation-free
     */
    public function getTreatments(): Collection
    {
        return $this->treatments;
    }

    /**
     * @param iterable<int,  Treatment> $treatments
     *
     * @return $this
     */
    public function setTreatments(iterable $treatments): self
    {
        ArrayCollectionUtils::update($this->treatments, $treatments, function (Treatment $treatment) {
            $treatment->setProtocol($this);
        });

        return $this;
    }

    /**
     * @return $this
     */
    public function addTreatments(Treatment $treatment): self
    {
        if (!$this->treatments->contains($treatment)) {
            $this->treatments->add($treatment);
            $treatment->setProtocol($this);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function removeTreatments(Treatment $treatment): self
    {
        if ($this->treatments->contains($treatment)) {
            $this->treatments->removeElement($treatment);
        }

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getExperiment(): ?Experiment
    {
        return $this->experiment;
    }

    /**
     * @return $this
     */
    public function setExperiment(?Experiment $experiment): self
    {
        $this->site = null;
        $this->experiment = $experiment;

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getOwner(): ?User
    {
        return $this->owner;
    }

    /**
     * @return $this
     */
    public function setOwner(?User $owner): self
    {
        $this->owner = $owner;

        return $this;
    }

    /**
     * @return $this
     */
    public function setDeletedAt(?\DateTime $deletedAt = null): self
    {
        $this->deletedAt = $deletedAt;
        if (null === $deletedAt) {
            foreach ($this->getTreatments() as $child) {
                $child->setDeletedAt($deletedAt);
            }
            foreach ($this->getFactors() as $child) {
                $child->setDeletedAt($deletedAt);
            }
        }

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getAlgorithm(): ?Algorithm
    {
        return $this->algorithm;
    }

    /**
     * @return $this
     */
    public function setAlgorithm(?Algorithm $algorithm): self
    {
        $this->algorithm = $algorithm;

        return $this;
    }

    /**
     * @return Collection<int,  ProtocolAttachment>
     *
     * @psalm-mutation-free
     */
    public function getProtocolAttachments(): Collection
    {
        return $this->protocolAttachments;
    }

    /**
     * @param iterable<int,  ProtocolAttachment> $protocolAttachments
     *
     * @return $this
     */
    public function setProtocolAttachments(iterable $protocolAttachments): self
    {
        ArrayCollectionUtils::update($this->protocolAttachments, $protocolAttachments, function (ProtocolAttachment $protocolAttachment) {
            $protocolAttachment->setProtocol($this);
        });

        return $this;
    }

    /**
     * @return $this
     */
    public function addProtocolAttachment(ProtocolAttachment $protocolAttachment): self
    {
        if (!$this->protocolAttachments->contains($protocolAttachment)) {
            $this->protocolAttachments->add($protocolAttachment);
            $protocolAttachment->setProtocol($this);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function removeProtocolAttachment(ProtocolAttachment $protocolAttachment): self
    {
        if ($this->protocolAttachments->contains($protocolAttachment)) {
            $this->protocolAttachments->removeElement($protocolAttachment);
        }

        return $this;
    }
}
