<?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\PersonBundle\Entity\SocialWork;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Annotation\Groups;

#[DiscriminatorMap(typeProperty: 'type', mapping: ['social_issue' => SocialIssue::class])]
#[ORM\Entity]
#[ORM\Table(name: 'chill_person_social_issue')]
class SocialIssue
{
    /**
     * @var Collection<int, SocialIssue>
     */
    #[ORM\OneToMany(mappedBy: 'parent', targetEntity: SocialIssue::class)]
    private Collection $children;

    #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
    private ?\DateTimeInterface $desactivationDate = null;

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

    #[ORM\Column(name: 'ordering', type: Types::FLOAT, options: ['default' => '0.0'])]
    private float $ordering = 0.0;

    #[ORM\ManyToOne(targetEntity: SocialIssue::class, inversedBy: 'children')]
    private ?SocialIssue $parent = null;

    /**
     * @var Collection<int, SocialAction>
     */
    #[ORM\OneToMany(mappedBy: 'issue', targetEntity: SocialAction::class)]
    private Collection $socialActions;

    #[Groups(['read'])]
    #[ORM\Column(type: Types::JSON)]
    private array $title = [];

    public function __construct()
    {
        $this->children = new ArrayCollection();
        $this->socialActions = new ArrayCollection();
    }

    /**
     * @internal use @see{SocialIssue::setParent} instead
     *
     * @return $this
     */
    public function addChild(self $child): self
    {
        if (!$this->children->contains($child)) {
            $this->children[] = $child;
        }

        return $this;
    }

    public function addSocialAction(SocialAction $socialAction): self
    {
        if (!$this->socialActions->contains($socialAction)) {
            $this->socialActions[] = $socialAction;
            $socialAction->setIssue($this);
        }

        return $this;
    }

    /**
     * In a SocialIssues's collection, find the elements which are an ancestor of
     * other elements.
     *
     * Removing those elements of the Collection (which is not done by this method)
     * will ensure that only the most descendent elements are present in the collection,
     * (any ancestor of another element are present).
     *
     * @param Collection|SocialIssue[] $socialIssues
     *
     * @return Collection|SocialIssue[]
     */
    public static function findAncestorSocialIssues(array|Collection $socialIssues): Collection
    {
        $ancestors = new ArrayCollection();

        foreach ($socialIssues as $candidateChild) {
            if ($ancestors->contains($candidateChild)) {
                continue;
            }

            foreach ($socialIssues as $candidateParent) {
                if ($ancestors->contains($candidateParent)) {
                    continue;
                }

                if ($candidateChild->isDescendantOf($candidateParent)) {
                    $ancestors->add($candidateParent);
                }
            }
        }

        return $ancestors;
    }

    /**
     * get all the ancestors of the social issue.
     *
     * @param bool $includeThis if the array in the result must include the present SocialIssue
     *
     * @return list<SocialIssue>
     */
    public function getAncestors(bool $includeThis = true): array
    {
        $ancestors = [];

        if ($includeThis) {
            $ancestors[] = $this;
        }

        $current = $this;

        while ($current->hasParent()) {
            $ancestors[] = $current = $current->getParent();
        }

        return $ancestors;
    }

    /**
     * @return Collection|self[]
     */
    public function getChildren(): Collection
    {
        return $this->children;
    }

    public function getDesactivationDate(): ?\DateTimeInterface
    {
        return $this->desactivationDate;
    }

    /**
     * @return Collection|self[] All the descendants (children, children of children, ...)
     */
    public function getDescendants(): Collection
    {
        $descendants = new ArrayCollection();

        foreach ($this->getChildren() as $child) {
            if (!$descendants->contains($child)) {
                $descendants->add($child);

                foreach ($child->getDescendants() as $descendantsOfChild) {
                    if (!$descendants->contains($descendantsOfChild)) {
                        $descendants->add($descendantsOfChild);
                    }
                }
            }
        }

        return $descendants;
    }

    /**
     * @return Collection<int, SocialAction> All the descendant social actions of the entity
     */
    public function getDescendantsSocialActions(): Collection
    {
        $descendantsSocialActions = new ArrayCollection();

        foreach ($this->getSocialActions() as $socialAction) {
            foreach ($socialAction->getDescendantsWithThis() as $descendant) {
                if (!$descendantsSocialActions->contains($descendant)) {
                    $descendantsSocialActions->add($descendant);
                }
            }
        }

        return $descendantsSocialActions;
    }

    /**
     * @return Collection|self[] All the descendants including the current entity (this)
     */
    public function getDescendantsWithThis(): Collection
    {
        $descendants = $this->getDescendants();

        if (!$descendants->contains($this)) {
            $descendants->add($this);
        }

        return $descendants;
    }

    /**
     * @param array|SocialIssue[] $socialIssues
     */
    public static function getDescendantsWithThisForIssues(array|Collection $socialIssues): Collection
    {
        $unique = [];

        foreach ($socialIssues as $issue) {
            foreach ($issue->getDescendantsWithThis() as $child) {
                $unique[spl_object_hash($child)] = $child;
            }
        }

        return new ArrayCollection(array_values($unique));
    }

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

    public function getOrdering(): float
    {
        return $this->ordering;
    }

    public function getParent(): ?self
    {
        return $this->parent;
    }

    /**
     * @return Collection<int, SocialAction> All the social actions of the entity, it's
     *                                       the descendants and it's parents
     */
    public function getRecursiveSocialActions(): Collection
    {
        $recursiveSocialActions = new ArrayCollection();

        // Get social actions from parent issues
        foreach ($this->getAncestors(false) as $ancestor) {
            foreach ($ancestor->getDescendantsSocialActions() as $descendant) {
                $recursiveSocialActions->add($descendant);
            }
        }

        foreach ($this->getDescendantsWithThis() as $socialIssue) {
            foreach ($socialIssue->getDescendantsSocialActions() as $descendant) {
                $recursiveSocialActions->add($descendant);
            }
        }

        return $recursiveSocialActions;
    }

    /**
     * @return Collection<SocialAction>
     */
    public function getSocialActions(): Collection
    {
        return $this->socialActions;
    }

    public function getTitle(): array
    {
        return $this->title;
    }

    public function hasChildren(): bool
    {
        return 0 < $this->getChildren()->count();
    }

    public function hasParent(): bool
    {
        return null !== $this->parent;
    }

    /**
     * Recursive method which return true if the current $issue is a descendant
     * of the $issue given in parameter.
     */
    public function isDescendantOf(SocialIssue $issue): bool
    {
        if (!$this->hasParent()) {
            return false;
        }

        if ($this->getParent() === $issue) {
            return true;
        }

        return $this->getParent()->isDescendantOf($issue);
    }

    public function removeChild(self $child): self
    {
        if ($this->children->removeElement($child)) {
            // set the owning side to null (unless already changed)
            if ($child->getParent() === $this) {
                $child->setParent(null);
            }
        }

        return $this;
    }

    public function removeSocialAction(SocialAction $socialAction): self
    {
        if ($this->socialActions->removeElement($socialAction)) {
            // set the owning side to null (unless already changed)
            if ($socialAction->getIssue() === $this) {
                $socialAction->setIssue(null);
            }
        }

        return $this;
    }

    public function setDesactivationDate(?\DateTimeInterface $desactivationDate): self
    {
        $this->desactivationDate = $desactivationDate;

        return $this;
    }

    public function setOrdering(float $ordering): SocialIssue
    {
        $this->ordering = $ordering;

        return $this;
    }

    /**
     * @internal use @see{SocialIssue::addChild()} instead
     */
    public function setParent(?self $parent): self
    {
        $this->parent = $parent;

        if (null !== $parent) {
            $parent->addChild($this);
        }

        return $this;
    }

    public function setTitle(array $title): self
    {
        $this->title = $title;

        return $this;
    }
}
