<?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\SearchFilter;
use ApiPlatform\Core\Serializer\Filter\GroupFilter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Shared\Authentication\Entity\Site;
use Shared\Utils\ArrayCollectionUtils;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Webapp\Core\Enumeration\PathLevelEnum;
use Webapp\Core\Enumeration\VariableTypeEnum;
use Webapp\Core\Validator\UniqueAttributeInParent;

/**
 * @ApiResource(
 *     collectionOperations={
 *         "get"={},
 *         "post"={
 *              "security_post_denormalize"="is_granted('ROLE_PLATFORM_MANAGER', object.getSite())",
 *              "denormalization_context"={"groups"={"generator_variable_post"}}
 *          },
 *     },
 *     itemOperations={
 *          "get"={},
 *          "patch"={
 *              "security_post_denormalize"="is_granted('ROLE_PLATFORM_MANAGER', object.getSite())",
 *              "denormalization_context"={"groups"={"generator_variable_post"}}
 *          },
 *          "delete"={
 *              "security"="is_granted('ROLE_PLATFORM_MANAGER', object.getSite()) and is_granted('GENERATOR_VARIABLE_DELETE', object)"
 *          }
 *     }
 * )
 *
 * @ApiFilter(SearchFilter::class, properties={"site": "exact", "generatorVariable": "exact", "project": "exact", "projectData.sessions": "exact", "projectData": "exact"})
 * @ApiFilter(GroupFilter::class, arguments={"whitelist"={"project_explorer_view", "connected_variables"}})
 *
 * @ORM\Entity()
 *
 * @ORM\Table(name="variable_generator", schema="webapp")
 *
 * @psalm-import-type PathLevelEnumId from PathLevelEnum
 */
class GeneratorVariable extends AbstractVariable
{
    /**
     * @ORM\Column(type="string", nullable=false)
     *
     * @Groups({"project_explorer_view", "generator_variable_post", "semi_automatic_variable", "webapp_data_view", "data_explorer_view", "connected_variables", "data_entry_synthesis", "project_synthesis", "variable_synthesis", "fusion_result", "webapp_data_path", "webapp_data_fusion"})
     *
     * @Assert\NotBlank
     *
     * @UniqueAttributeInParent(parentsAttributes={"site.generatorVariables", "project.generatorVariables", "project.simpleVariables"})
     */
    protected string $name;

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

    /**
     * @ORM\Column(type="string")
     *
     * @Groups({"generator_variable_post"})
     */
    private string $generatedPrefix;

    /**
     * @ORM\Column(type="boolean")
     *
     * @Groups({"generator_variable_post"})
     */
    private bool $numeralIncrement;

    /**
     * @ORM\Column(type="boolean")
     *
     * @Groups({"generator_variable_post"})
     */
    private bool $pathWayHorizontal;

    /**
     * @ORM\Column(type="boolean")
     *
     * @Groups({"generator_variable_post"})
     */
    private bool $directCounting;

    /**
     * @var Collection<int, SimpleVariable>
     *
     * @ORM\OneToMany(targetEntity="Webapp\Core\Entity\SimpleVariable", mappedBy="generatorVariable", cascade={"persist", "remove"}, orphanRemoval=true)
     *
     * @Groups({"project_explorer_view", "generator_variable_post", "data_explorer_view", "webapp_data_view"})
     */
    private Collection $generatedSimpleVariables;

    /**
     * @var Collection<int, GeneratorVariable>
     *
     * @ORM\OneToMany(targetEntity="Webapp\Core\Entity\GeneratorVariable", mappedBy="generatorVariable", cascade={"persist", "remove"}, orphanRemoval=true)
     *
     * @Groups({"project_explorer_view", "generator_variable_post", "data_explorer_view", "webapp_data_view"})
     */
    private Collection $generatedGeneratorVariables;

    /**
     * @ORM\ManyToOne(targetEntity="Webapp\Core\Entity\GeneratorVariable",inversedBy="generatedGeneratorVariables")
     *
     * @Groups({"data_entry_synthesis"})
     */
    protected ?GeneratorVariable $generatorVariable = null;

    /**
     * @ORM\ManyToOne(targetEntity="Webapp\Core\Entity\Project", inversedBy="generatorVariables")
     *
     * @Groups({"generator_variable_post"})
     */
    protected ?Project $project = null;

    /**
     * @var Collection<int, Test>
     *
     * @ORM\OneToMany(targetEntity="Webapp\Core\Entity\Test",mappedBy="variableGenerator", cascade={"persist", "remove"}, orphanRemoval=true)
     *
     * @Groups({"project_explorer_view", "project_synthesis", "variable_synthesis"})
     */
    protected Collection $tests;

    /**
     * @Groups({"variable_synthesis"})
     *
     * @ORM\ManyToOne(targetEntity="Webapp\Core\Entity\ProjectData", inversedBy="generatorVariables")
     */
    protected ?ProjectData $projectData = null;

    /**
     * @var Collection<int, VariableConnection>
     *
     * @ORM\OneToMany(targetEntity="Webapp\Core\Entity\VariableConnection", mappedBy="projectGeneratorVariable", cascade={"persist", "remove"}, orphanRemoval=true)
     *
     * @Groups({"project_explorer_view", "connected_variables", "project_synthesis"})
     */
    protected Collection $connectedVariables;

    /**
     * @param PathLevelEnumId|'' $pathLevel
     */
    public function __construct(string $name = '', string $shortName = '', int $repetitions = 0, string $pathLevel = '', bool $mandatory = false, string $generatedPrefix = '', bool $numeralIncrement = false, bool $pathWayHorizontal = false, bool $directCounting = false)
    {
        parent::__construct();
        $this->name = $name; // TODO delete when https://github.com/api-platform/core/issues/3974 is solved
        $this->shortName = $shortName; // TODO delete when https://github.com/api-platform/core/issues/3974 is solved
        $this->repetitions = $repetitions; // TODO delete when https://github.com/api-platform/core/issues/3974 is solved
        $this->pathLevel = $pathLevel; // TODO delete when https://github.com/api-platform/core/issues/3974 is solved
        $this->mandatory = $mandatory; // TODO delete when https://github.com/api-platform/core/issues/3974 is solved
        $this->generatedPrefix = $generatedPrefix; // TODO delete when https://github.com/api-platform/core/issues/3974 is solved
        $this->numeralIncrement = $numeralIncrement; // TODO delete when https://github.com/api-platform/core/issues/3974 is solved
        $this->pathWayHorizontal = $pathWayHorizontal; // TODO delete when https://github.com/api-platform/core/issues/3974 is solved
        $this->directCounting = $directCounting; // TODO delete when https://github.com/api-platform/core/issues/3974 is solved
        $this->generatedSimpleVariables = new ArrayCollection();
        $this->generatedGeneratorVariables = new ArrayCollection();
    }

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

    /**
     * @psalm-mutation-free
     */
    public function getType(): string
    {
        return VariableTypeEnum::INTEGER;
    }

    /**
     * @return array<array-key, SimpleVariable|GeneratorVariable>
     *
     * @Groups("project_synthesis")
     */
    public function getGeneratedVariables(): array
    {
        return array_merge(
            $this->getGeneratedSimpleVariables()->getValues(),
            $this->getGeneratedGeneratorVariables()->getValues()
        );
    }

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

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

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function isNumeralIncrement(): bool
    {
        return $this->numeralIncrement;
    }

    /**
     * @return $this
     */
    public function setNumeralIncrement(bool $numeralIncrement): self
    {
        $this->numeralIncrement = $numeralIncrement;

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function isPathWayHorizontal(): bool
    {
        return $this->pathWayHorizontal;
    }

    /**
     * @return $this
     */
    public function setPathWayHorizontal(bool $pathWayHorizontal): self
    {
        $this->pathWayHorizontal = $pathWayHorizontal;

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function isDirectCounting(): bool
    {
        return $this->directCounting;
    }

    /**
     * @return $this
     */
    public function setDirectCounting(bool $directCounting): self
    {
        $this->directCounting = $directCounting;

        return $this;
    }

    /**
     * @return Collection<int,  SimpleVariable>
     *
     * @psalm-mutation-free
     */
    public function getGeneratedSimpleVariables(): Collection
    {
        return $this->generatedSimpleVariables;
    }

    /**
     * @param iterable<array-key, SimpleVariable> $generatedSimpleVariables
     *
     * @return $this
     */
    public function setGeneratedSimpleVariables(iterable $generatedSimpleVariables): self
    {
        ArrayCollectionUtils::update($this->generatedSimpleVariables, $generatedSimpleVariables, function (SimpleVariable $simpleVariable) {
            $simpleVariable->setGeneratorVariable($this);
        });

        return $this;
    }

    /**
     * @return $this
     */
    public function addGeneratedSimpleVariable(SimpleVariable $generatedSimpleVariable): self
    {
        if (!$this->generatedSimpleVariables->contains($generatedSimpleVariable)) {
            $this->generatedSimpleVariables->add($generatedSimpleVariable);
            $generatedSimpleVariable->setGeneratorVariable($this);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function removeGeneratedSimpleVariable(SimpleVariable $generatedSimpleVariable): self
    {
        if ($this->generatedSimpleVariables->contains($generatedSimpleVariable)) {
            $this->generatedSimpleVariables->removeElement($generatedSimpleVariable);
            $generatedSimpleVariable->setGeneratorVariable(null);
        }

        return $this;
    }

    /**
     * @return Collection<int,  GeneratorVariable>
     *
     * @psalm-mutation-free
     */
    public function getGeneratedGeneratorVariables(): Collection
    {
        return $this->generatedGeneratorVariables;
    }

    /**
     * @param iterable<array-key, GeneratorVariable> $generatedGeneratorVariables
     *
     * @return $this
     */
    public function setGeneratedGeneratorVariables(iterable $generatedGeneratorVariables): self
    {
        ArrayCollectionUtils::update($this->generatedGeneratorVariables, $generatedGeneratorVariables, function (GeneratorVariable $generatorVariable) {
            $generatorVariable->setGeneratorVariable($this);
        });

        return $this;
    }

    /**
     * @return $this
     */
    public function addGeneratedGeneratorVariable(self $generatedGeneratorVariable): self
    {
        if (!$this->generatedGeneratorVariables->contains($generatedGeneratorVariable)) {
            $this->generatedGeneratorVariables->add($generatedGeneratorVariable);
            $generatedGeneratorVariable->setGeneratorVariable($this);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function removeGeneratedGeneratorVariable(self $generatedGeneratorVariable): self
    {
        if ($this->generatedGeneratorVariables->contains($generatedGeneratorVariable)) {
            $this->generatedGeneratorVariables->removeElement($generatedGeneratorVariable);
            $generatedGeneratorVariable->setGeneratorVariable(null);
        }

        return $this;
    }

    /**
     * @param array<array-key,  SimpleVariable|GeneratorVariable> $variables
     *
     * @return $this
     */
    public function setGeneratedVariable(array $variables): self
    {
        $this->setGeneratedSimpleVariables(array_filter($variables, fn (AbstractVariable $variable) => $variable instanceof SimpleVariable));
        $this->setGeneratedGeneratorVariables(array_filter($variables, fn (AbstractVariable $variable) => $variable instanceof GeneratorVariable));

        return $this;
    }

    /**
     * @psalm-param SimpleVariable|GeneratorVariable $variables
     *
     * @return $this
     */
    public function addGeneratedVariable(AbstractVariable $variable): self
    {
        if ($variable instanceof SimpleVariable) {
            $this->addGeneratedSimpleVariable($variable);
        } elseif ($variable instanceof self) {
            $this->addGeneratedGeneratorVariable($variable);
        } else {
            throw new \LogicException('Invalid argument form $variable.');
        }

        return $this;
    }

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

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

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getGeneratorVariable(): ?self
    {
        return $this->generatorVariable;
    }

    /**
     * @return $this
     */
    public function setGeneratorVariable(?self $generatorVariable): self
    {
        $this->generatorVariable = $generatorVariable;
        $this->project = null;
        $this->site = null;

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getProject(): ?Project
    {
        return $this->project;
    }

    /**
     * @return $this
     */
    public function setProject(?Project $project): self
    {
        $this->project = $project;
        $this->site = null;
        $this->generatorVariable = null;

        return $this;
    }

    /**
     * @psalm-mutation-free
     */
    public function getProjectData(): ?ProjectData
    {
        return $this->projectData;
    }

    /**
     * @return $this
     */
    public function setProjectData(?ProjectData $projectData): self
    {
        $this->projectData = $projectData;

        return $this;
    }
}
