<?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\Enumeration\Annotation\EnumType;
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\Annotation\GraphicallyDeletable;
use Webapp\Core\ApiOperation\RestoreObjectOperation;
use Webapp\Core\Dto\BusinessObject\BusinessObjectCsvOutputDto;
use Webapp\Core\Entity\Attachment\PlatformAttachment;
use Webapp\Core\Enumeration\GraphicalOriginEnum;
use Webapp\Core\Traits\GraphicallyDeletableEntity;
use Webapp\Core\Validator\UniqueAttributeInParent;

/**
 * @ApiResource(
 *     collectionOperations={
 *         "get"={},
 *         "post"={
 *              "security_post_denormalize"="is_granted('ROLE_PLATFORM_MANAGER', object.getSite())",
 *              "denormalization_context"={"groups"={"platform_post"}},
 *          },
 *     },
 *     itemOperations={
 *          "get"={},
 *          "patch"={
 *              "security"="is_granted('ROLE_PLATFORM_MANAGER', object.getSite())",
 *              "denormalization_context"={"groups"={"update_platform"}}
 *          },
 *          "delete"={
 *              "security"="is_granted('ROLE_PLATFORM_MANAGER', object.getSite()) && object.getProjects().count() === 0"
 *          },
 *          "restore"={
 *              "controller"=RestoreObjectOperation::class,
 *              "method"="PATCH",
 *              "path"="/platforms/{id}/restore",
 *              "security"="is_granted('ROLE_SITE_ADMIN')",
 *              "read"=false,
 *              "validate"=false,
 *              "openapi_context"={
 *                  "summary": "Restore deleted protocol",
 *                  "description": "Remove the deleted state"
 *              },
 *          },
 *          "export"={
 *              "method"="GET",
 *              "path"="/platforms/{id}/exportCsv",
 *              "formats"={"csv"={"text/csv"}},
 *              "pagination_enabled"=false,
 *              "output"=BusinessObjectCsvOutputDto::class
 *          }
 *     }
 * )
 *
 * @ApiFilter(SearchFilter::class, properties={"site": "exact", "name": "exact", "projects": "exact", "experiments": "exact"})
 * @ApiFilter(ExistsFilter::class, properties={"deletedAt"})
 * @ApiFilter(GroupFilter::class, arguments={"whitelist"={
 *     "design_explorer_view",
 *     "platform_full_view",
 *     "admin_explorer_view",
 *     "platform_synthesis",
 *     "project_explorer_view"
 * }})
 * @ApiFilter(DeletedFilter::class)
 *
 * @AdvancedRight(classIdentifier="webapp_platform", ownerField="owner", siteAttribute="site")
 *
 * @Gedmo\SoftDeleteable()
 *
 * @GraphicallyDeletable()
 *
 * @ORM\Entity()
 *
 * @ORM\Table(name="platform", schema="webapp")
 */
class Platform extends IdentifiedEntity implements BusinessObject
{
    use GraphicallyDeletableEntity;

    use HasOwnerEntity;

    use SoftDeleteableEntity;

    /**
     * @ORM\Column(type="string")
     *
     * @Groups({"design_explorer_view", "update_platform", "platform_full_view", "project_explorer_view", "data_explorer_view", "webapp_data_view", "admin_explorer_view", "data_entry_synthesis", "platform_synthesis", "project_synthesis", "platform_post", "variable_synthesis"})
     *
     * @Assert\NotBlank
     *
     * @UniqueAttributeInParent(parentsAttributes={"site.platforms"})
     */
    private string $name = '';

    /**
     * @ORM\Column(type="string")
     *
     * @Groups({"platform_full_view", "update_platform", "platform_synthesis", "platform_post"})
     */
    private string $siteName = '';

    /**
     * @ORM\Column(type="string")
     *
     * @Groups({"platform_full_view", "update_platform", "platform_synthesis", "platform_post"})
     */
    private string $placeName = '';

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

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

    /**
     * @var Collection<int, Experiment>
     *
     * @ORM\OneToMany(targetEntity="Webapp\Core\Entity\Experiment", mappedBy="platform", cascade={"persist", "remove"}, orphanRemoval=true)
     *
     * @Groups({"design_explorer_view", "platform_full_view", "platform_synthesis", "platform_post"})
     */
    private Collection $experiments;

    /**
     * @var Collection<int, Project>
     *
     * @ORM\OneToMany(targetEntity="Webapp\Core\Entity\Project", mappedBy="platform", cascade={"persist", "remove"})
     *
     * @Groups({"design_explorer_view", "platform_synthesis", "project_explorer_view", "data_explorer_view"})
     */
    private Collection $projects;

    /**
     * @Groups({"platform_synthesis", "change_report", "data_entry_synthesis", "project_synthesis", "platform_post", "variable_synthesis", "status_project_mobile_view"})
     *
     * @ORM\ManyToOne(targetEntity="Shared\Authentication\Entity\Site", inversedBy="platforms")
     */
    private Site $site;

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

    /**
     * @ORM\Column(type="integer", nullable=true)
     *
     * @Groups({"platform_full_view"})
     */
    private ?int $color = null;

    /**
     * @ORM\Column(type="integer", nullable=true)
     *
     * @Groups({"platform_full_view", "update_platform"})
     */
    private int $xMesh = 50;

    /**
     * @ORM\Column(type="integer", nullable=true)
     *
     * @Groups({"platform_full_view", "update_platform"})
     */
    private int $yMesh = 50;

    /**
     * @ORM\Column(type="integer", nullable=true)
     *
     * @EnumType(class="Webapp\Core\Enumeration\GraphicalOriginEnum")
     *
     * @Groups({"platform_full_view", "update_platform"})
     */
    private int $origin = GraphicalOriginEnum::TOP_LEFT;

    /**
     * @var Collection<int, PlatformAttachment>
     *
     * @ORM\OneToMany(targetEntity="Webapp\Core\Entity\Attachment\PlatformAttachment", mappedBy="platform", cascade={"persist", "remove"})
     *
     * @Groups({"platform_post"})
     */
    private Collection $platformAttachments;

    /**
     * @ORM\OneToOne (targetEntity="Webapp\Core\Entity\NorthIndicator", mappedBy="platform", cascade={"persist", "remove"})
     *
     * @Groups({"platform_full_view"})
     */
    private ?NorthIndicator $northIndicator = null;

    /**
     * @var Collection<int, GraphicalTextZone>
     *
     * @ORM\OneToMany (targetEntity="Webapp\Core\Entity\GraphicalTextZone", mappedBy="platform", cascade={"persist", "remove"})
     *
     * @Groups({"platform_full_view"})
     */
    private Collection $graphicalTextZones;

    public function __construct()
    {
        $this->experiments = new ArrayCollection();
        $this->projects = new ArrayCollection();
        $this->platformAttachments = new ArrayCollection();
        $this->graphicalTextZones = new ArrayCollection();
        $this->created = new \DateTime();
    }

    /**
     * @Groups({"platform_full_view", "design_explorer_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 getSiteName(): string
    {
        return $this->siteName;
    }

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

        return $this;
    }

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

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

        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,  Experiment>
     *
     * @psalm-mutation-free
     */
    public function getExperiments(): Collection
    {
        return $this->experiments;
    }

    /**
     * @param iterable<int,  Experiment> $experiments
     *
     * @return $this
     */
    public function setExperiments(iterable $experiments): self
    {
        ArrayCollectionUtils::update($this->experiments, $experiments, function (Experiment $experiment) {
            $experiment->setPlatform($this);
        });

        return $this;
    }

    /**
     * @return $this
     */
    public function addExperiment(Experiment $experiment): self
    {
        if (!$this->experiments->contains($experiment)) {
            $this->experiments->add($experiment);
            $experiment->setPlatform($this);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function removeExperiment(Experiment $experiment): self
    {
        if ($this->experiments->contains($experiment)) {
            $this->experiments->removeElement($experiment);
            $experiment->setPlatform(null);
        }

        return $this;
    }

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

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

        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 Collection<int,  Project>
     *
     * @psalm-mutation-free
     */
    public function getProjects(): Collection
    {
        return $this->projects;
    }

    /**
     * @param iterable<int,  Project> $projects
     *
     * @return $this
     */
    public function setProjects(iterable $projects): self
    {
        ArrayCollectionUtils::update($this->projects, $projects, function (Project $project) {
            $project->setPlatform($this);
        });

        return $this;
    }

    /**
     * @return $this
     */
    public function addProject(Project $project): self
    {
        if (!$this->projects->contains($project)) {
            $this->projects->add($project);
            $project->setPlatform($this);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function removeProject(Project $project): self
    {
        if ($this->projects->contains($project)) {
            $this->projects->removeElement($project);
            $project->setPlatform(null);
        }

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getColor(): ?int
    {
        return $this->color;
    }

    /**
     * @return $this
     */
    public function setColor(?int $color): self
    {
        $this->color = $color;

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getXMesh(): int
    {
        return $this->xMesh;
    }

    /**
     * @return $this
     */
    public function setXMesh(int $xMesh): self
    {
        $this->xMesh = $xMesh;

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getYMesh(): int
    {
        return $this->yMesh;
    }

    /**
     * @return $this
     */
    public function setYMesh(int $yMesh): self
    {
        $this->yMesh = $yMesh;

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getOrigin(): int
    {
        return $this->origin;
    }

    /**
     * @return $this
     */
    public function setOrigin(int $origin): self
    {
        $this->origin = $origin;

        return $this;
    }

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

        return $this;
    }

    /**
     * @return Collection<int,  PlatformAttachment>
     *
     * @psalm-mutation-free
     */
    public function getPlatformAttachments(): Collection
    {
        return $this->platformAttachments;
    }

    /**
     * @param iterable<int,  PlatformAttachment> $platformAttachments
     *
     * @return $this
     */
    public function setPlatformAttachments(iterable $platformAttachments): self
    {
        ArrayCollectionUtils::update($this->platformAttachments, $platformAttachments, function (PlatformAttachment $platformAttachment) {
            $platformAttachment->setPlatform($this);
        });

        return $this;
    }

    /**
     * @return $this
     */
    public function addPlatformAttachment(PlatformAttachment $platformAttachment): self
    {
        if (!$this->platformAttachments->contains($platformAttachment)) {
            $this->platformAttachments->add($platformAttachment);
            $platformAttachment->setPlatform($this);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function removePlatformAttachment(PlatformAttachment $platformAttachment): self
    {
        if ($this->platformAttachments->contains($platformAttachment)) {
            $this->platformAttachments->removeElement($platformAttachment);
        }

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getNorthIndicator(): ?NorthIndicator
    {
        return $this->northIndicator;
    }

    /**
     * @return $this
     */
    public function setNorthIndicator(?NorthIndicator $northIndicator): self
    {
        $this->northIndicator = $northIndicator;

        return $this;
    }

    /**
     * @return Collection<int,  GraphicalTextZone>
     *
     * @psalm-mutation-free
     */
    public function getGraphicalTextZones(): Collection
    {
        return $this->graphicalTextZones;
    }

    /**
     * @param iterable<int,  GraphicalTextZone> $graphicalTextZones
     *
     * @return $this
     */
    public function setGraphicalTextZones(iterable $graphicalTextZones): self
    {
        ArrayCollectionUtils::update($this->graphicalTextZones, $graphicalTextZones, function (GraphicalTextZone $graphicalTextZone) {
            $graphicalTextZone->setPlatform($this);
        });

        return $this;
    }

    /**
     * @return $this
     */
    public function addGraphicalTextZone(GraphicalTextZone $graphicalTextZone): self
    {
        if (!$this->graphicalTextZones->contains($graphicalTextZone)) {
            $this->graphicalTextZones->add($graphicalTextZone);
            $graphicalTextZone->setPlatform($this);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function removeGraphicalTextZone(GraphicalTextZone $graphicalTextZone): self
    {
        if ($this->graphicalTextZones->contains($graphicalTextZone)) {
            $this->graphicalTextZones->removeElement($graphicalTextZone);
        }

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function parent(): ?BusinessObject
    {
        return null;
    }

    /**
     * @return Experiment[]
     */
    public function children(): array
    {
        return [...$this->experiments->getValues()];
    }
}
