<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\Entity\Workflow;

use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserGroup;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * A step for each EntityWorkflow.
 *
 * The step contains the history of position. The current one is the one which transitionAt or transitionAfter is NULL.
 *
 * The comments field is populated by the comment of the one who apply the transition, it means that the comment for the
 * "next" step is stored in the EntityWorkflowStep in the previous step.
 *
 * DestUsers are the one added at the transition. DestUserByAccessKey are the users who obtained permission after having
 * clicked on a link to get access (email notification to groups).
 */
#[ORM\Entity]
#[ORM\Table('chill_main_workflow_entity_step')]
class EntityWorkflowStep
{
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false)]
    private string $accessKey;

    /**
     * @var Collection<int, User>
     */
    #[ORM\ManyToMany(targetEntity: User::class)]
    #[ORM\JoinTable(name: 'chill_main_workflow_entity_step_cc_user')]
    private Collection $ccUser;

    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, options: ['default' => ''])]
    private string $comment = '';

    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
    private ?string $currentStep = '';

    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)]
    private array $destEmail = [];

    /**
     * @var Collection<int, User>
     */
    #[ORM\ManyToMany(targetEntity: User::class)]
    #[ORM\JoinTable(name: 'chill_main_workflow_entity_step_user')]
    private Collection $destUser;

    /**
     * @var Collection<int, UserGroup>
     */
    #[ORM\ManyToMany(targetEntity: UserGroup::class)]
    #[ORM\JoinTable(name: 'chill_main_workflow_entity_step_user_group')]
    private Collection $destUserGroups;

    /**
     * @var Collection<int, User>
     */
    #[ORM\ManyToMany(targetEntity: User::class)]
    #[ORM\JoinTable(name: 'chill_main_workflow_entity_step_user_by_accesskey')]
    private Collection $destUserByAccessKey;

    /**
     * @var Collection <int, EntityWorkflowStepSignature>
     */
    #[ORM\OneToMany(mappedBy: 'step', targetEntity: EntityWorkflowStepSignature::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
    private Collection $signatures;

    #[ORM\ManyToOne(targetEntity: EntityWorkflow::class, inversedBy: 'steps')]
    private ?EntityWorkflow $entityWorkflow = null;

    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])]
    private bool $freezeAfter = false;

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
    private ?int $id = null;

    /**
     * If this is the final step.
     *
     * This property is filled by a listener.
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])]
    private bool $isFinal = false;

    /**
     * filled by @see{EntityWorkflow::getStepsChained}.
     */
    private ?EntityWorkflowStep $next = null;

    /**
     * filled by @see{EntityWorkflow::getStepsChained}.
     */
    private ?EntityWorkflowStep $previous = null;

    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: true, options: ['default' => null])]
    private ?string $transitionAfter = null;

    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true, options: ['default' => null])]
    private ?\DateTimeImmutable $transitionAt = null;

    #[ORM\ManyToOne(targetEntity: User::class)]
    #[ORM\JoinColumn(nullable: true)]
    private ?User $transitionBy = null;

    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: true)]
    private ?string $transitionByEmail = null;

    /**
     * @var Collection<int, EntityWorkflowStepHold>
     */
    #[ORM\OneToMany(mappedBy: 'step', targetEntity: EntityWorkflowStepHold::class, cascade: ['remove'])]
    private Collection $holdsOnStep;

    /**
     * @var Collection<int, EntityWorkflowSend>
     */
    #[ORM\OneToMany(mappedBy: 'entityWorkflowStep', targetEntity: EntityWorkflowSend::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
    private Collection $sends;

    public function __construct()
    {
        $this->ccUser = new ArrayCollection();
        $this->destUser = new ArrayCollection();
        $this->destUserGroups = new ArrayCollection();
        $this->destUserByAccessKey = new ArrayCollection();
        $this->signatures = new ArrayCollection();
        $this->holdsOnStep = new ArrayCollection();
        $this->sends = new ArrayCollection();
        $this->accessKey = bin2hex(openssl_random_pseudo_bytes(32));
    }

    public function addCcUser(User $user): self
    {
        if (!$this->ccUser->contains($user)) {
            $this->ccUser[] = $user;
        }

        return $this;
    }

    /**
     * @deprecated
     */
    public function addDestEmail(string $email): self
    {
        if (!\in_array($email, $this->destEmail, true)) {
            $this->destEmail[] = $email;
        }

        return $this;
    }

    public function addDestUser(User $user): self
    {
        if (!$this->destUser->contains($user)) {
            $this->destUser[] = $user;
        }

        return $this;
    }

    public function addDestUserGroup(UserGroup $userGroup): self
    {
        if (!$this->destUserGroups->contains($userGroup)) {
            $this->destUserGroups[] = $userGroup;
        }

        return $this;
    }

    public function removeDestUserGroup(UserGroup $userGroup): self
    {
        $this->destUserGroups->removeElement($userGroup);

        return $this;
    }

    public function addDestUserByAccessKey(User $user): self
    {
        if (!$this->destUserByAccessKey->contains($user) && !$this->destUser->contains($user)) {
            $this->destUserByAccessKey[] = $user;
        }

        return $this;
    }

    /**
     * @internal use @see{EntityWorkflowStepSignature}'s constructor instead
     */
    public function addSignature(EntityWorkflowStepSignature $signature): self
    {
        if (!$this->signatures->contains($signature)) {
            $this->signatures[] = $signature;
        }

        return $this;
    }

    /**
     * @internal use @see{EntityWorkflowSend}'s constructor instead
     */
    public function addSend(EntityWorkflowSend $send): self
    {
        if (!$this->sends->contains($send)) {
            $this->sends[] = $send;
        }

        return $this;
    }

    public function removeSignature(EntityWorkflowStepSignature $signature): self
    {
        if ($this->signatures->contains($signature)) {
            $this->signatures->removeElement($signature);
        }

        return $this;
    }

    public function getAccessKey(): string
    {
        return $this->accessKey;
    }

    /**
     * get all the users which are allowed to apply a transition: those added manually, and
     * those added automatically by using an access key.
     *
     * This method exclude the users associated with user groups
     *
     * @psalm-suppress DuplicateArrayKey
     */
    public function getAllDestUser(): Collection
    {
        return new ArrayCollection(
            [
                ...$this->getDestUser()->toArray(),
                ...$this->getDestUserByAccessKey()->toArray(),
            ]
        );
    }

    /**
     * @return Collection<int, UserGroup>
     */
    public function getDestUserGroups(): Collection
    {
        return $this->destUserGroups;
    }

    public function getCcUser(): Collection
    {
        return $this->ccUser;
    }

    /**
     * This is the comment from the one who apply the transition.
     *
     * It means that it must be saved when the user apply a transition.
     */
    public function getComment(): string
    {
        return $this->comment;
    }

    public function getCurrentStep(): ?string
    {
        return $this->currentStep;
    }

    /**
     * @return array<string>
     *
     * @deprecated
     */
    public function getDestEmail(): array
    {
        return $this->destEmail;
    }

    /**
     * get dest users added by the creator.
     *
     * You should **not** rely on this method to get all users which are able to
     * apply a transition on this step. Use @see{EntityWorkflowStep::getAllDestUser} instead.
     */
    public function getDestUser(): Collection
    {
        return $this->destUser;
    }

    public function getDestUserByAccessKey(): Collection
    {
        return $this->destUserByAccessKey;
    }

    public function getEntityWorkflow(): ?EntityWorkflow
    {
        return $this->entityWorkflow;
    }

    /**
     * @return Collection<int, EntityWorkflowStepSignature>
     */
    public function getSignatures(): Collection
    {
        return $this->signatures;
    }

    /**
     * @return Collection<int, EntityWorkflowSend>
     */
    public function getSends(): Collection
    {
        return $this->sends;
    }

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

    public function getNext(): ?EntityWorkflowStep
    {
        return $this->next;
    }

    public function getPrevious(): ?EntityWorkflowStep
    {
        return $this->previous;
    }

    public function getTransitionAfter(): ?string
    {
        return $this->transitionAfter;
    }

    public function getTransitionAt(): ?\DateTimeImmutable
    {
        return $this->transitionAt;
    }

    public function getTransitionBy(): ?User
    {
        return $this->transitionBy;
    }

    public function getTransitionByEmail(): ?string
    {
        return $this->transitionByEmail;
    }

    /**
     * @return bool true if this is the end of the EntityWorkflow
     */
    public function isFinal(): bool
    {
        return $this->isFinal;
    }

    public function isFreezeAfter(): bool
    {
        return $this->freezeAfter;
    }

    public function isOnHoldByUser(User $user): bool
    {
        foreach ($this->getHoldsOnStep() as $onHold) {
            if ($onHold->getByUser() === $user) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return bool if the EntityWorkflowStep is waiting for a transition, and is not the final step
     */
    public function isWaitingForTransition(): bool
    {
        if (null !== $this->transitionAfter) {
            return false;
        }

        if ($this->isFinal()) {
            return false;
        }

        return true;
    }

    public function removeCcUser(User $user): self
    {
        $this->ccUser->removeElement($user);

        return $this;
    }

    public function removeDestEmail(string $email): self
    {
        $this->destEmail = array_filter($this->destEmail, static fn (string $existing) => $email !== $existing);

        return $this;
    }

    public function removeDestUser(User $user): self
    {
        $this->destUser->removeElement($user);

        return $this;
    }

    public function removeDestUserByAccessKey(User $user): self
    {
        $this->destUserByAccessKey->removeElement($user);

        return $this;
    }

    public function setComment(?string $comment): EntityWorkflowStep
    {
        $this->comment = (string) $comment;

        return $this;
    }

    public function setCurrentStep(?string $currentStep): EntityWorkflowStep
    {
        $this->currentStep = $currentStep;

        return $this;
    }

    public function setDestEmail(array $destEmail): EntityWorkflowStep
    {
        $this->destEmail = $destEmail;

        return $this;
    }

    /**
     * @internal use @see(EntityWorkflow::addStep} instead
     */
    public function setEntityWorkflow(?EntityWorkflow $entityWorkflow): EntityWorkflowStep
    {
        $this->entityWorkflow = $entityWorkflow;

        return $this;
    }

    public function setFreezeAfter(bool $freezeAfter): EntityWorkflowStep
    {
        $this->freezeAfter = $freezeAfter;

        return $this;
    }

    public function setIsFinal(bool $isFinal): EntityWorkflowStep
    {
        $this->isFinal = $isFinal;

        return $this;
    }

    /**
     * @internal
     */
    public function setNext(?EntityWorkflowStep $next): self
    {
        $this->next = $next;

        return $this;
    }

    /**
     * @internal
     */
    public function setPrevious(?EntityWorkflowStep $previous): self
    {
        $this->previous = $previous;

        return $this;
    }

    public function setTransitionAfter(?string $transitionAfter): EntityWorkflowStep
    {
        $this->transitionAfter = $transitionAfter;

        return $this;
    }

    public function setTransitionAt(?\DateTimeImmutable $transitionAt): EntityWorkflowStep
    {
        $this->transitionAt = $transitionAt;

        return $this;
    }

    public function setTransitionBy(?User $transitionBy): EntityWorkflowStep
    {
        $this->transitionBy = $transitionBy;

        return $this;
    }

    public function setTransitionByEmail(?string $transitionByEmail): EntityWorkflowStep
    {
        $this->transitionByEmail = $transitionByEmail;

        return $this;
    }

    public function getHoldsOnStep(): Collection
    {
        return $this->holdsOnStep;
    }

    public function addOnHold(EntityWorkflowStepHold $onHold): self
    {
        if (!$this->holdsOnStep->contains($onHold)) {
            $this->holdsOnStep->add($onHold);
        }

        return $this;
    }
}
