<?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;

use Chill\BudgetBundle\Entity\Charge;
use Chill\BudgetBundle\Entity\Resource;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\Language;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
use Chill\PersonBundle\Entity\Identifier\PersonIdentifier;
use Chill\PersonBundle\Entity\Person\PersonCenterCurrent;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Entity\Person\PersonCurrentAddress;
use Chill\PersonBundle\Entity\Person\PersonResource;
use Chill\PersonBundle\PersonIdentifier\Validator\RequiredIdentifierConstraint;
use Chill\PersonBundle\Validator\Constraints\Household\HouseholdMembershipSequential;
use Chill\PersonBundle\Validator\Constraints\Person\Birthdate;
use Chill\PersonBundle\Validator\Constraints\Person\PersonHasCenter;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Mapping as ORM;
use libphonenumber\PhoneNumber;
use Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber as MisdPhoneNumberConstraint;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

/**
 * Person Class.
 */
#[DiscriminatorMap(typeProperty: 'type', mapping: ['person' => Person::class])]
#[ORM\Entity]
#[ORM\Index(name: 'person_names', columns: ['firstName', 'lastName'])] // ,
#[ORM\Index(name: 'person_birthdate', columns: ['birthdate'])] // @ORM\HasLifecycleCallbacks
#[ORM\Table(name: 'chill_person_person')]
#[ORM\HasLifecycleCallbacks]
#[PersonHasCenter]
#[HouseholdMembershipSequential(groups: ['household_memberships'])]
class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateInterface, \Stringable
{
    // have days in commun
    final public const ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD = 2; // where there exist

    final public const ERROR_PERIODS_ARE_COLLAPSING = 1; // when two different periods

    /**
     * Accept receiving email.
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])]
    private ?bool $acceptEmail = false;

    /**
     * Accept short text message (aka SMS).
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])]
    private ?bool $acceptSMS = false;

    /**
     * The person's accompanying periods (when the person was accompanied by the center).
     *
     * @var Collection<int, AccompanyingPeriodParticipation>
     */
    #[ORM\OneToMany(targetEntity: AccompanyingPeriodParticipation::class, mappedBy: 'person', cascade: ['persist', 'remove', 'merge', 'detach'])]
    #[ORM\OrderBy(['startDate' => Criteria::DESC])]
    private Collection $accompanyingPeriodParticipations;

    /**
     * The accompanying period requested by the Person.
     *
     * @var Collection<int, AccompanyingPeriod>
     */
    #[ORM\OneToMany(targetEntity: AccompanyingPeriod::class, mappedBy: 'requestorPerson')]
    private Collection $accompanyingPeriodRequested;

    /**
     * Addresses.
     *
     * @var Collection<int, Address>
     */
    #[ORM\ManyToMany(targetEntity: Address::class, cascade: ['persist', 'remove', 'merge', 'detach'])]
    #[ORM\JoinTable(name: 'chill_person_persons_to_addresses')]
    #[ORM\OrderBy(['validFrom' => Criteria::DESC])]
    private Collection $addresses;

    /**
     * @var Collection<int, PersonAltName>
     */
    #[ORM\OneToMany(targetEntity: PersonAltName::class, mappedBy: 'person', cascade: ['persist', 'remove', 'merge', 'detach'], orphanRemoval: true)]
    private Collection $altNames;

    /**
     * The person's birthdate.
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_MUTABLE, nullable: true)]
    #[Birthdate]
    private ?\DateTime $birthdate = null;

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

    /**
     * @var Collection<int, \Chill\BudgetBundle\Entity\Resource>
     */
    #[ORM\OneToMany(targetEntity: Resource::class, mappedBy: 'person')]
    private Collection $budgetResources;

    /**
     * @var Collection<int, Calendar>
     */
    #[ORM\ManyToMany(targetEntity: Calendar::class, mappedBy: 'persons')]
    private Collection $calendars;

    /**
     * @var Collection<int, Calendar>&Selectable<int, Calendar>
     */
    #[ORM\OneToMany(mappedBy: 'person', targetEntity: Calendar::class)]
    private Collection $directCalendars;

    /**
     * The person's center.
     *
     * @deprecated
     */
    #[ORM\ManyToOne(targetEntity: Center::class)]
    private ?Center $center = null;

    #[ORM\OneToOne(mappedBy: 'person', targetEntity: PersonCenterCurrent::class)]
    private ?PersonCenterCurrent $centerCurrent = null;

    /**
     * @var Collection<int, PersonCenterHistory>&Selectable
     */
    #[ORM\OneToMany(mappedBy: 'person', targetEntity: PersonCenterHistory::class, cascade: ['persist', 'remove'])]
    private Collection&Selectable $centerHistory;

    /**
     * Array where customfield's data are stored.
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)]
    private ?array $cFData = null;

    /**
     * The marital status of the person.
     */
    #[ORM\ManyToOne(targetEntity: Civility::class)]
    #[ORM\JoinColumn(nullable: true)]
    private ?Civility $civility = null;

    /**
     * Contact information for contacting the person.
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: true)]
    private ?string $contactInfo = '';

    /**
     * The person's country of birth.
     */
    #[ORM\ManyToOne(targetEntity: Country::class)] // sf4 check: option inversedBy="birthsIn" return error mapping !!
    #[ORM\JoinColumn(nullable: true)]
    private ?Country $countryOfBirth = null;

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

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

    /**
     * Cache the computation of household.
     */
    private array $currentHouseholdAt = [];

    /**
     * Cache for the computation of current household participation.
     */
    private array $currentHouseholdParticipationAt = [];

    /**
     * The current person address.
     *
     * This is computed through database and is optimized on database side.
     */
    #[ORM\OneToOne(targetEntity: PersonCurrentAddress::class, mappedBy: 'person')]
    private ?PersonCurrentAddress $currentPersonAddress = null;

    /**
     * The person's deathdate.
     */
    #[Assert\GreaterThanOrEqual(propertyPath: 'birthdate')]
    #[Assert\LessThanOrEqual('today')]
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_IMMUTABLE, nullable: true)]
    private ?\DateTimeImmutable $deathdate = null;

    /**
     * The person's email.
     */
    #[Assert\Email]
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: true)]
    private ?string $email = '';

    /**
     * The person's first name.
     */
    #[Assert\NotBlank(message: 'The firstname cannot be empty')]
    #[Assert\Length(max: 255)]
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
    private string $firstName = '';

    /**
     * fullname canonical. Read-only field, which is calculated by
     * the database.
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: true)]
    private ?string $fullnameCanonical = '';

    /**
     * NEW column : The person's gender.
     */
    #[Assert\NotNull(message: 'The gender must be set')]
    #[ORM\ManyToOne(targetEntity: Gender::class)]
    private ?Gender $gender = null;

    /**
     * Comment on gender.
     */
    #[ORM\Embedded(class: CommentEmbeddable::class, columnPrefix: 'genderComment_')]
    private CommentEmbeddable $genderComment;

    /**
     * Read-only field, computed by the database.
     *
     * @var Collection<int, PersonHouseholdAddress>
     */
    #[ORM\OneToMany(targetEntity: PersonHouseholdAddress::class, mappedBy: 'person')]
    private Collection $householdAddresses;

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

    /**
     * The person's id.
     */
    #[ORM\Id]
    #[ORM\Column(name: 'id', type: \Doctrine\DBAL\Types\Types::INTEGER)]
    #[ORM\GeneratedValue(strategy: 'AUTO')]
    private ?int $id = null;

    #[ORM\OneToMany(mappedBy: 'person', targetEntity: PersonIdentifier::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
    #[RequiredIdentifierConstraint]
    #[Assert\Valid]
    private Collection $identifiers;

    /**
     * The person's last name.
     */
    #[Assert\NotBlank(message: 'The lastname cannot be empty')]
    #[Assert\Length(max: 255)]
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
    private string $lastName = '';

    /**
     * The marital status of the person.
     */
    #[ORM\ManyToOne(targetEntity: MaritalStatus::class)]
    #[ORM\JoinColumn(nullable: true)]
    private ?MaritalStatus $maritalStatus = null;

    /**
     * Comment on marital status.
     */
    #[ORM\Embedded(class: CommentEmbeddable::class, columnPrefix: 'maritalStatusComment_')]
    private CommentEmbeddable $maritalStatusComment;

    /**
     * The date of the last marital status change of the person.
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_MUTABLE, nullable: true)]
    private ?\DateTime $maritalStatusDate = null;

    /**
     * A remark over the person.
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
    private string $memo = '';

    /**
     * The person's administrative status.
     */
    #[ORM\ManyToOne(targetEntity: AdministrativeStatus::class)]
    #[ORM\JoinColumn(nullable: true)]
    private ?AdministrativeStatus $administrativeStatus = null;

    /**
     * The person's mobile phone number.
     */
    #[ORM\Column(type: 'phone_number', nullable: true)]
    #[MisdPhoneNumberConstraint(type: [MisdPhoneNumberConstraint::MOBILE])]
    private ?PhoneNumber $mobilenumber = null;

    /**
     * The person's professional status.
     */
    #[ORM\ManyToOne(targetEntity: EmploymentStatus::class)]
    #[ORM\JoinColumn(nullable: true)]
    private ?EmploymentStatus $employmentStatus = null;

    /**
     * The person's nationality.
     */
    #[ORM\ManyToOne(targetEntity: Country::class)] // sf4 check: option inversedBy="nationals" return error mapping !!
    #[ORM\JoinColumn(nullable: true)]
    private ?Country $nationality = null;

    /**
     * Number of children.
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true)]
    private ?int $numberOfChildren = null;

    /**
     * @var Collection<int, PersonPhone>
     */
    #[Assert\Valid(traverse: true)]
    #[ORM\OneToMany(targetEntity: PersonPhone::class, mappedBy: 'person', cascade: ['persist', 'remove', 'merge', 'detach'], orphanRemoval: true)]
    private Collection $otherPhoneNumbers;

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

    /**
     * The person's phonenumber.
     */
    #[ORM\Column(type: 'phone_number', nullable: true)]
    #[MisdPhoneNumberConstraint(type: [MisdPhoneNumberConstraint::FIXED_LINE, MisdPhoneNumberConstraint::VOIP, MisdPhoneNumberConstraint::PERSONAL_NUMBER])]
    private ?PhoneNumber $phonenumber = null;

    /**
     * The person's place of birth.
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255, name: 'place_of_birth')]
    private string $placeOfBirth = '';

    /**
     * @deprecated
     */
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN)]
    private bool $proxyAccompanyingPeriodOpenState = false; // TO-DELETE ?
    /**
     * @var Collection<int, PersonResource>
     */
    #[ORM\OneToMany(targetEntity: PersonResource::class, mappedBy: 'personOwner')]
    private Collection $resources;

    /**
     * The person's spoken languages.
     *
     * @var Collection<int, Language>
     */
    #[ORM\ManyToMany(targetEntity: Language::class)]
    #[ORM\JoinTable(name: 'persons_spoken_languages', joinColumns: [new ORM\JoinColumn(name: 'person_id', referencedColumnName: 'id')], inverseJoinColumns: [new ORM\JoinColumn(name: 'language_id', referencedColumnName: 'id')])]
    private Collection $spokenLanguages;

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

    #[ORM\ManyToOne(targetEntity: User::class)]
    private ?User $updatedBy = null;

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

    /**
     * Person constructor.
     */
    public function __construct()
    {
        $this->calendars = new ArrayCollection();
        $this->directCalendars = new ArrayCollection();
        $this->accompanyingPeriodParticipations = new ArrayCollection();
        $this->spokenLanguages = new ArrayCollection();
        $this->addresses = new ArrayCollection();
        $this->altNames = new ArrayCollection();
        $this->otherPhoneNumbers = new ArrayCollection();
        $this->householdParticipations = new ArrayCollection();
        $this->householdAddresses = new ArrayCollection();
        $this->genderComment = new CommentEmbeddable();
        $this->maritalStatusComment = new CommentEmbeddable();
        $this->periodLocatedOn = new ArrayCollection();
        $this->accompanyingPeriodRequested = new ArrayCollection();
        $this->budgetResources = new ArrayCollection();
        $this->budgetCharges = new ArrayCollection();
        $this->resources = new ArrayCollection();
        $this->centerHistory = new ArrayCollection();
        $this->signatures = new ArrayCollection();
        $this->identifiers = new ArrayCollection();
    }

    public function __toString(): string
    {
        return $this->getLabel();
    }

    /**
     * Add AccompanyingPeriodParticipation.
     *
     * @uses AccompanyingPeriod::addPerson
     */
    public function addAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): self
    {
        $participation = new AccompanyingPeriodParticipation($accompanyingPeriod, $this);
        $this->accompanyingPeriodParticipations->add($participation);

        return $this;
    }

    public function addAddress(Address $address): self
    {
        $this->addresses[] = $address;

        return $this;
    }

    public function addAltName(PersonAltName $altName): self
    {
        if (false === $this->altNames->contains($altName)) {
            $this->altNames->add($altName);
            $altName->setPerson($this);
        }

        return $this;
    }

    public function addBudgetCharge(Charge $budgetCharge): self
    {
        $this->budgetCharges[] = $budgetCharge;

        return $this;
    }

    public function addBudgetResource(Resource $budgetResource): self
    {
        $this->budgetResources[] = $budgetResource;

        return $this;
    }

    public function addHouseholdParticipation(HouseholdMember $member): self
    {
        $this->householdParticipations[] = $member;

        return $this;
    }

    /**
     * @return $this
     */
    public function addOtherPhoneNumber(PersonPhone $otherPhoneNumber)
    {
        if (false === $this->otherPhoneNumbers->contains($otherPhoneNumber)) {
            $otherPhoneNumber->setPerson($this);
            $this->otherPhoneNumbers->add($otherPhoneNumber);
        }

        return $this;
    }

    public function addSignature(EntityWorkflowStepSignature $signature): self
    {
        if (!$this->signatures->contains($signature)) {
            $this->signatures->add($signature);
        }

        return $this;
    }

    public function addIdentifier(PersonIdentifier $identifier): self
    {
        if (!$this->identifiers->contains($identifier)) {
            $this->identifiers[] = $identifier;
            $identifier->setPerson($this);
        }

        return $this;
    }

    public function removeIdentifier(PersonIdentifier $identifier): self
    {
        $this->identifiers->removeElement($identifier);
        $identifier->setPerson(null);

        return $this;
    }

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

        return $this;
    }

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

    public function getSignaturesPending(): ReadableCollection
    {
        return $this->signatures->filter(fn (EntityWorkflowStepSignature $signature) => EntityWorkflowSignatureStateEnum::PENDING === $signature->getState());
    }

    /**
     * Function used for validation that check if the accompanying periods of
     * the person are not collapsing (i.e. have not shared days) or having
     * a period after an open period.
     *
     * @return true|array True if the accompanying periods are not collapsing,
     *                    an array with data for displaying the error
     */
    public function checkAccompanyingPeriodsAreNotCollapsing(): array|bool
    {
        $periods = $this->getAccompanyingPeriodsOrdered();
        $periodsNbr = \count($periods);
        $i = 0;

        while ($periodsNbr - 1 > $i) {
            $periodI = $periods[$i];
            $periodAfterI = $periods[$i + 1];

            if ($periodI->isOpen()) {
                return [
                    'result' => self::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD,
                    'dateOpening' => $periodAfterI->getOpeningDate(),
                    'dateClosing' => $periodAfterI->getClosingDate(),
                    'date' => $periodI->getOpeningDate(),
                ];
            }

            if ($periodI->getClosingDate() >= $periodAfterI->getOpeningDate()) {
                return [
                    'result' => self::ERROR_PERIODS_ARE_COLLAPSING,
                    'dateOpening' => $periodI->getOpeningDate(),

                    'dateClosing' => $periodI->getClosingDate(),
                    'date' => $periodAfterI->getOpeningDate(),
                ];
            }
            ++$i;
        }

        return true;
    }

    /**
     * Set the Person file as closed at the given date.
     *
     * For update a closing date, you should update AccompanyingPeriod instance
     * directly.
     *
     * To check if the Person and its accompanying period are consistent, use validation.
     *
     * @throws \Exception if two lines of the accompanying period are open
     */
    public function close(?AccompanyingPeriod $accompanyingPeriod = null): void
    {
        $this->proxyAccompanyingPeriodOpenState = false;
    }

    public function countResources(): int
    {
        return $this->resources->count();
    }

    /**
     * This public function is the same but return only true or false.
     */
    public function containsAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
    {
        return ($this->participationsContainAccompanyingPeriod($accompanyingPeriod)) ? false : true;
    }

    /**
     * Handy method to get the AccompanyingPeriodParticipation
     * matching a given AccompanyingPeriod.
     *
     * Used in template, to find the participation when iterating on a list
     * of period.
     */
    public function findParticipationForPeriod(AccompanyingPeriod $period): ?AccompanyingPeriodParticipation
    {
        $closeCandidates = [];

        foreach ($this->getAccompanyingPeriodParticipations() as $participation) {
            if ($participation->getAccompanyingPeriod() === $period) {
                if ($participation->isOpen()) {
                    return $participation;
                }
                $closeCandidates[] = $participation;
            }
        }

        if (0 < \count($closeCandidates)) {
            return $closeCandidates[0];
        }

        return null;
    }

    public function getAcceptEmail(): ?bool
    {
        return $this->acceptEmail;
    }

    public function getAcceptSMS(): ?bool
    {
        return $this->acceptSMS;
    }

    /**
     * Return a list of all accompanying period where the person is involved:.
     *
     * * as requestor;
     * * as participant, only for opened participation;
     *
     * @param bool $asParticipantOpen add participation which are still opened
     * @param bool $asRequestor       add accompanying period where the person is requestor
     *
     * @return AccompanyingPeriod[]|Collection
     */
    public function getAccompanyingPeriodInvolved(
        bool $asParticipantOpen = true,
        bool $asRequestor = true,
    ): Collection {
        $result = new ArrayCollection();

        if ($asParticipantOpen) {
            foreach ($this->getAccompanyingPeriodParticipations()
                ->map(fn (AccompanyingPeriodParticipation $app) => $app->getAccompanyingPeriod()) as $period
            ) {
                if (!$result->contains($period)) {
                    $result->add($period);
                }
            }
        }

        if ($asRequestor) {
            foreach ($this->accompanyingPeriodRequested as $period) {
                if (!$result->contains($period)) {
                    $result->add($period);
                }
            }
        }

        return $result;
    }

    public function countAccompanyingPeriodInvolved(
        bool $asParticipantOpen = true,
        bool $asRequestor = true,
    ): int {
        // TODO should be optimized to avoid loading accompanying period ?
        return $this->getAccompanyingPeriodInvolved($asParticipantOpen, $asRequestor)
            ->filter(fn (AccompanyingPeriod $p) => AccompanyingPeriod::STEP_DRAFT !== $p->getStep())
            ->count();
    }

    /**
     * Get AccompanyingPeriodParticipations Collection.
     *
     * @return AccompanyingPeriodParticipation[]|Collection
     */
    public function getAccompanyingPeriodParticipations(): Collection
    {
        return $this->accompanyingPeriodParticipations;
    }

    /**
     * @return AccompanyingPeriod[]|Collection
     */
    public function getAccompanyingPeriodRequested(): Collection
    {
        return $this->accompanyingPeriodRequested;
    }

    /**
     * Get AccompanyingPeriods array.
     */
    public function getAccompanyingPeriods(): array
    {
        $accompanyingPeriods = [];

        foreach ($this->accompanyingPeriodParticipations as $participation) {
            /* @var AccompanyingPeriodParticipation $participation */
            $accompanyingPeriods[] = $participation->getAccompanyingPeriod();
        }

        return $accompanyingPeriods;
    }

    /**
     * Get the accompanying periods of a give person with the chronological order.
     */
    public function getAccompanyingPeriodsOrdered(): array
    {
        $periods = $this->getAccompanyingPeriods();

        // order by date :
        usort($periods, static function ($a, $b) {
            $dateA = $a->getOpeningDate();
            $dateB = $b->getOpeningDate();

            if ($dateA === $dateB) {
                $dateEA = $a->getClosingDate();
                $dateEB = $b->getClosingDate();

                if ($dateEA === $dateEB) {
                    return 0;
                }

                if ($dateEA < $dateEB) {
                    return -1;
                }

                return +1;
            }

            if ($dateA < $dateB) {
                return -1;
            }

            return 1;
        });

        return $periods;
    }

    /**
     * get the address associated with the person at the given date.
     *
     * If the `$at` parameter is now, use the method `getCurrentPersonAddress`, which is optimized
     * on database side.
     *
     * @deprecated since chill2.0, address is linked to the household. Use @see{Person::getCurrentHouseholdAddress}
     *
     * @throws \Exception
     */
    public function getAddressAt(?\DateTimeInterface $at = null): ?Address
    {
        $at ??= new \DateTime('now');

        if ($at instanceof \DateTimeImmutable) {
            $at = \DateTime::createFromImmutable($at);
        }

        /** @var \ArrayIterator $addressesIterator */
        $addressesIterator = $this->getAddresses()
            ->filter(static fn (Address $address): bool => $address->getValidFrom() <= $at)
            ->getIterator();

        $addressesIterator->uasort(
            static fn (Address $left, Address $right): int => $right->getValidFrom() <=> $left->getValidFrom()
        );

        return [] === ($addresses = iterator_to_array($addressesIterator)) ?
            null :
            current($addresses);
    }

    /**
     * By default, the addresses are ordered by date, descending (the most
     * recent first).
     */
    public function getAddresses(): Collection
    {
        return $this->addresses;
    }

    public function getAdministrativeStatus(): ?AdministrativeStatus
    {
        return $this->administrativeStatus;
    }

    /**
     * Return the age of a person, calculated at the date 'now'.
     *
     * If the person has a deathdate, calculate the age at the deathdate.
     *
     * @param string $at a valid string to create a DateTime
     */
    public function getAge(string $at = 'now'): ?int
    {
        if ($this->birthdate instanceof \DateTimeInterface) {
            if ($this->deathdate instanceof \DateTimeInterface) {
                return (int) date_diff($this->birthdate, $this->deathdate)->format('%y');
            }

            return (int) date_diff($this->birthdate, date_create($at))->format('%y');
        }

        return null;
    }

    public function getAltNames(): Collection
    {
        return $this->altNames;
    }

    public function getBirthdate(): ?\DateTime
    {
        return $this->birthdate;
    }

    /**
     * @return Collection|Charge[]
     */
    public function getBudgetCharges(): Collection
    {
        return $this->budgetCharges;
    }

    /**
     * @return Collection|\Chill\BudgetBundle\Entity\Resource[]
     */
    public function getBudgetResources(): Collection
    {
        return $this->budgetResources;
    }

    /**
     * @return Collection<int, Calendar>
     */
    public function getCalendars(): Collection
    {
        return $this->calendars;
    }

    /**
     * Get next calendars for this person (calendars with start date after today).
     * Only returns calendars that are directly linked to this person via the person property,
     * not those linked through AccompanyingPeriods.
     *
     * @param int|null $limit Optional limit for the number of results
     *
     * @return array<Calendar>
     */
    public function getNextCalendarsForPerson(?int $limit = null): array
    {
        $today = new \DateTimeImmutable('today');

        $criteria = Criteria::create()
            ->where(Criteria::expr()->gte('startDate', $today))
            ->orderBy(['startDate' => Order::Ascending]);

        if (null !== $limit) {
            $criteria->setMaxResults($limit);
        }

        return $this->directCalendars->matching($criteria)->toArray();
    }

    public function getCenter(): ?Center
    {
        if (null !== $this->centerCurrent) {
            return $this->centerCurrent->getCenter();
        }

        if (null === $currentCenterHistory = $this->getCurrentCenterHistory()) {
            return null;
        }

        return $currentCenterHistory->getCenter();
    }

    public function getCenterCurrent(): ?PersonCenterCurrent
    {
        if (null !== $this->centerCurrent) {
            return $this->centerCurrent;
        }

        if (null === $currentCenterHistory = $this->getCurrentCenterHistory()) {
            return null;
        }

        return new PersonCenterCurrent($currentCenterHistory);
    }

    public function getCenterHistory(): Collection
    {
        return $this->centerHistory;
    }

    public function getCFData(): ?array
    {
        if (null === $this->cFData) {
            $this->cFData = [];
        }

        return $this->cFData;
    }

    public function getCivility(): ?Civility
    {
        return $this->civility;
    }

    public function getcontactInfo(): ?string
    {
        return $this->contactInfo;
    }

    public function getCountryOfBirth(): ?Country
    {
        return $this->countryOfBirth;
    }

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

    public function getCreatedBy(): ?User
    {
        return $this->createdBy;
    }

    /**
     * Returns the opened accompanying period.
     *
     * @deprecated since 1.1 use `getOpenedAccompanyingPeriod instead
     */
    public function getCurrentAccompanyingPeriod(): ?AccompanyingPeriod
    {
        return $this->getOpenedAccompanyingPeriod();
    }

    /**
     * Get current accompanyingPeriods array.
     *
     * @return AccompanyingPeriod[]|array
     */
    public function getCurrentAccompanyingPeriods(): array
    {
        $currentAccompanyingPeriods = [];
        $currentDate = new \DateTime();

        foreach ($this->accompanyingPeriodParticipations as $participation) {
            $endDate = $participation->getEndDate();

            if (null === $endDate || $endDate > $currentDate) {
                $currentAccompanyingPeriods[] = $participation->getAccompanyingPeriod();
            }
        }

        return $currentAccompanyingPeriods;
    }

    public function getCurrentHousehold(?\DateTimeImmutable $at = null): ?Household
    {
        $participation = $this->getCurrentHouseholdParticipationShareHousehold($at);

        return $participation instanceof HouseholdMember ?
            $participation->getHousehold()
            : null;
    }

    /**
     * Get the household address at the given date.
     *
     * if the given date is 'now', use instead @see{getCurrentPersonAddress}, which is optimized on
     * database side.
     */
    public function getCurrentHouseholdAddress(?\DateTimeImmutable $at = null): ?Address
    {
        if (
            null === $at
            || $at->format('Ymd') === (new \DateTime('today'))->format('Ymd')
        ) {
            return $this->currentPersonAddress instanceof PersonCurrentAddress
                ? $this->currentPersonAddress->getAddress() : null;
        }

        // if not now, compute the date from history
        $criteria = new Criteria();
        $expr = Criteria::expr();

        $criteria->where(
            $expr->lte('validFrom', $at)
        )
            ->andWhere(
                $expr->orX(
                    $expr->isNull('validTo'),
                    $expr->gte('validTo', $at)
                )
            );

        $addrs = $this->getHouseholdAddresses()
            ->matching($criteria);

        if ($addrs->count() > 0) {
            return $addrs->first()->getAddress();
        }

        return null;
    }

    public function getCurrentHouseholdParticipationShareHousehold(?\DateTimeImmutable $at = null): ?HouseholdMember
    {
        $criteria = new Criteria();
        $expr = Criteria::expr();
        $date = $at ?? new \DateTimeImmutable('today');
        $datef = $date->format('Y-m-d');

        if (
            null !== ($this->currentHouseholdParticipationAt[$datef] ?? null)) {
            return $this->currentHouseholdParticipationAt[$datef];
        }

        $criteria
            ->where(
                $expr->andX(
                    $expr->lte('startDate', $date),
                    $expr->orX(
                        $expr->isNull('endDate'),
                        $expr->gt('endDate', $date)
                    ),
                    $expr->eq('shareHousehold', true)
                )
            );

        $participations = $this->getHouseholdParticipations()
            ->matching($criteria);

        return $participations->count() > 0 ?
            $this->currentHouseholdParticipationAt[$datef] = $participations->first()
            : null;
    }

    /**
     * Get the current person address.
     */
    public function getCurrentPersonAddress(): ?Address
    {
        if (null === $this->currentPersonAddress) {
            return null;
        }

        return $this->currentPersonAddress->getAddress();
    }

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

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function getFirstName(): string
    {
        return $this->firstName;
    }

    public function getFullnameCanonical(): string
    {
        return $this->fullnameCanonical;
    }

    public function getEmploymentStatus(): ?EmploymentStatus
    {
        return $this->employmentStatus;
    }

    public function getGender(): ?Gender
    {
        return $this->gender;
    }

    public function getGenderComment(): ?CommentEmbeddable
    {
        return $this->genderComment;
    }

    public function getHouseholdAddresses(): Collection
    {
        return $this->householdAddresses;
    }

    /**
     * @return Collection|HouseholdMember[]
     */
    public function getHouseholdParticipations(): Collection
    {
        return $this->householdParticipations;
    }

    /**
     * Get participation where the person does not share the household.
     *
     * Order by startDate, desc
     *
     * @return HouseholdMember[]
     */
    public function getHouseholdParticipationsNotShareHousehold(): Collection
    {
        $criteria = new Criteria();
        $expr = Criteria::expr();

        $criteria
            ->where(
                $expr->eq('shareHousehold', false)
            )
            ->orderBy(['startDate' => Order::Descending]);

        return $this->getHouseholdParticipations()
            ->matching($criteria);
    }

    /**
     * Get participation where the person does share the household.
     *
     * Order by startDate, desc
     *
     * @return Collection|HouseholdMember[]
     */
    public function getHouseholdParticipationsShareHousehold(): Collection
    {
        $criteria = new Criteria();
        $expr = Criteria::expr();

        $criteria
            ->where(
                $expr->eq('shareHousehold', true)
            )
            ->orderBy(['startDate' => Order::Descending, 'id' => Order::Descending]);

        return $this->getHouseholdParticipations()
            ->matching($criteria);
    }

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

    /**
     * @return ReadableCollection<int, PersonIdentifier>
     */
    public function getIdentifiers(): ReadableCollection
    {
        return $this->identifiers;
    }

    /**
     * @return string
     */
    public function getLabel()
    {
        return $this->getFirstName().' '.$this->getLastName();
    }

    /**
     * @deprecated Use @see{Person::getCurrentPersonAddress} or @see{Person::getCurrentHouseholdAddress} instead
     *
     * @return false|mixed|null
     *
     * @throws \Exception
     */
    public function getLastAddress(?\DateTime $from = null)
    {
        return $this->getCurrentHouseholdAddress(
            null !== $from ? \DateTimeImmutable::createFromMutable($from) : null
        );
    }

    public function getLastName(): string
    {
        return $this->lastName;
    }

    public function getMaritalStatus(): ?MaritalStatus
    {
        return $this->maritalStatus;
    }

    public function getMaritalStatusComment(): CommentEmbeddable
    {
        return $this->maritalStatusComment;
    }

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

    public function getMemo(): ?string
    {
        return $this->memo;
    }

    public function getMobilenumber(): ?PhoneNumber
    {
        return $this->mobilenumber;
    }

    public function getNationality(): ?Country
    {
        return $this->nationality;
    }

    public function getNumberOfChildren(): ?int
    {
        return $this->numberOfChildren;
    }

    /**
     * Return the opened accompanying period.
     */
    public function getOpenedAccompanyingPeriod(): ?AccompanyingPeriod
    {
        if (false === $this->isOpen()) {
            return null;
        }

        foreach ($this->accompanyingPeriodParticipations as $participation) {
            /** @var AccompanyingPeriodParticipation $participation */
            if ($participation->getAccompanyingPeriod()->isOpen()) {
                return $participation->getAccompanyingPeriod();
            }
        }

        return null;
    }

    /**
     * Return a collection of participation, where the participation
     * is still opened or in draft state.
     *
     * @return AccompanyingPeriodParticipation[]|Collection
     */
    public function getOpenedParticipations(): Collection
    {
        // create a criteria for filtering easily
        $criteria = Criteria::create();
        $criteria
            ->andWhere(Criteria::expr()->eq('endDate', null))
            ->orWhere(Criteria::expr()->gt('endDate', new \DateTime('now')));

        return $this->getAccompanyingPeriodParticipations()
            ->matching($criteria)
            ->filter(static fn (AccompanyingPeriodParticipation $app) => AccompanyingPeriod::STEP_CLOSED !== $app->getAccompanyingPeriod()->getStep());
    }

    public function getOtherPhoneNumbers(): Collection
    {
        return $this->otherPhoneNumbers;
    }

    public function getPhonenumber(): ?PhoneNumber
    {
        return $this->phonenumber;
    }

    public function getPlaceOfBirth(): ?string
    {
        return $this->placeOfBirth;
    }

    /**
     * @return PersonResource[]|Collection
     */
    public function getResources(): array|Collection
    {
        return $this->resources;
    }

    /**
     * Get spokenLanguages.
     *
     * @return Collection<Language>
     */
    public function getSpokenLanguages()
    {
        return $this->spokenLanguages;
    }

    public function addSpokenLanguage(Language $language): self
    {
        if (!$this->spokenLanguages->contains($language)) {
            $this->spokenLanguages->add($language);
        }

        return $this;
    }

    public function removeSpokenLanguage(Language $language): self
    {
        $this->spokenLanguages->removeElement($language);

        return $this;
    }

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

    public function getUpdatedBy(): ?User
    {
        return $this->updatedBy;
    }

    public function hasCurrentHouseholdAddress(?\DateTimeImmutable $at = null): bool
    {
        return null !== $this->getCurrentHouseholdAddress($at);
    }

    /**
     * Return true if the person has two addresses with the
     * same validFrom date (in format 'Y-m-d').
     */
    public function hasTwoAdressWithSameValidFromDate()
    {
        $validYMDDates = [];

        foreach ($this->addresses as $ad) {
            $validDate = $ad->getValidFrom()->format('Y-m-d');

            if (\in_array($validDate, $validYMDDates, true)) {
                return true;
            }
            $validYMDDates[] = $validDate;
        }

        return false;
    }

    /**
     * Validation callback that checks if the accompanying periods are valid.
     *
     * This method add violation errors.
     */
    #[Assert\Callback(groups: ['accompanying_period_consistent'])]
    public function isAccompanyingPeriodValid(ExecutionContextInterface $context)
    {
        $r = $this->checkAccompanyingPeriodsAreNotCollapsing();

        if (true !== $r) {
            if (self::ERROR_PERIODS_ARE_COLLAPSING === $r['result']) {
                $context->buildViolation('Two accompanying periods have days in commun')
                    ->atPath('accompanyingPeriods')
                    ->addViolation();
            }

            if (self::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD === $r['result']) {
                $context->buildViolation('A period is opened and a period is added after it')
                    ->atPath('accompanyingPeriods')
                    ->addViolation();
            }
        }
    }

    /**
     * Validation callback that checks if the addresses are valid (do not have
     * two addresses with the same validFrom date).
     *
     * This method add violation errors.
     */
    #[Assert\Callback(groups: ['addresses_consistent'])]
    public function isAddressesValid(ExecutionContextInterface $context)
    {
        if ($this->hasTwoAdressWithSameValidFromDate()) {
            $context
                ->buildViolation('Two addresses has the same validFrom date')
                ->atPath('addresses')
                ->addViolation();
        }
    }

    /**
     * Check if the person is opened.
     */
    public function isOpen(): bool
    {
        foreach ($this->getAccompanyingPeriods() as $period) {
            if ($period->isOpen()) {
                return true;
            }
        }

        return false;
    }

    public function isSharingHousehold(?\DateTimeImmutable $at = null): bool
    {
        return null !== $this->getCurrentHousehold($at);
    }

    /**
     * set the Person file as open at the given date.
     *
     * For updating a opening's date, you should update AccompanyingPeriod instance
     * directly.
     *
     * For closing a file, @see this::close
     *
     * To check if the Person and its accompanying period is consistent, use validation.
     */
    public function open(AccompanyingPeriod $accompanyingPeriod): void
    {
        $this->proxyAccompanyingPeriodOpenState = true;
        $this->addAccompanyingPeriod($accompanyingPeriod);
    }

    /**
     * Remove AccompanyingPeriod.
     */
    public function removeAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): void
    {
        $participation = $this->participationsContainAccompanyingPeriod($accompanyingPeriod);

        if (!null === $participation) {
            $participation->setEndDate(new \DateTime());
            $this->accompanyingPeriodParticipations->removeElement($participation);
        }
    }

    public function removeAddress(Address $address)
    {
        $this->addresses->removeElement($address);
    }

    public function removeAltName(PersonAltName $altName): self
    {
        if ($this->altNames->contains($altName)) {
            $altName->setPerson(null);
            $this->altNames->removeElement($altName);
        }

        return $this;
    }

    public function removeBudgetCharge(Charge $budgetCharge): self
    {
        $this->budgetCharges->removeElement($budgetCharge);

        return $this;
    }

    public function removeBudgetResource(Resource $budgetResource): self
    {
        $this->budgetResources->removeElement($budgetResource);

        return $this;
    }

    public function removeOtherPhoneNumber(PersonPhone $otherPhoneNumber): self
    {
        if ($this->otherPhoneNumbers->contains($otherPhoneNumber)) {
            $this->otherPhoneNumbers->removeElement($otherPhoneNumber);
        }

        return $this;
    }

    public function setAcceptEmail(bool $acceptEmail): self
    {
        $this->acceptEmail = $acceptEmail;

        return $this;
    }

    public function setAdministrativeStatus(?AdministrativeStatus $administrativeStatus): self
    {
        $this->administrativeStatus = $administrativeStatus;

        return $this;
    }

    public function setAcceptSMS(bool $acceptSMS): self
    {
        $this->acceptSMS = $acceptSMS;

        return $this;
    }

    public function setAltNames(Collection $altNames): self
    {
        $this->altNames = $altNames;

        return $this;
    }

    public function setBirthdate(?\DateTime $birthdate): self
    {
        $this->birthdate = $birthdate;

        return $this;
    }

    /**
     * Associate the center with the person. The association start on 'now'.
     *
     * @return $this
     */
    public function setCenter(?Center $center): self
    {
        $modification = new \DateTimeImmutable('now');

        foreach ($this->centerHistory as $centerHistory) {
            if (null === $centerHistory->getEndDate()) {
                $centerHistory->setEndDate($modification);
            }
        }

        if (null === $center) {
            return $this;
        }

        $this->centerHistory[] = new PersonCenterHistory($this, $center, $modification);

        return $this;
    }

    public function setCenterHistory(Collection&Selectable $centerHistory): Person
    {
        $this->centerHistory = $centerHistory;

        return $this;
    }

    public function addCenterHistory(PersonCenterHistory $newCenterHistory): self
    {
        if (!$this->centerHistory->contains($newCenterHistory)) {
            $this->centerHistory[] = $newCenterHistory;
            $newCenterHistory->setPerson($this);
        }

        return $this;
    }

    public function setCFData(?array $cFData): self
    {
        $this->cFData = $cFData;

        return $this;
    }

    public function setCivility(?Civility $civility = null): self
    {
        $this->civility = $civility;

        return $this;
    }

    public function setcontactInfo($contactInfo): self
    {
        if (null === $contactInfo) {
            $contactInfo = '';
        }

        $this->contactInfo = $contactInfo;

        return $this;
    }

    public function setCountryOfBirth(?Country $countryOfBirth = null): self
    {
        $this->countryOfBirth = $countryOfBirth;

        return $this;
    }

    public function setCreatedAt(\DateTimeInterface $datetime): self
    {
        $this->createdAt = $datetime;

        return $this;
    }

    public function setCreatedBy(User $createdBy): self
    {
        $this->createdBy = $createdBy;

        return $this;
    }

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

        return $this;
    }

    public function setEmail(?string $email): self
    {
        $this->email = trim((string) $email);

        return $this;
    }

    public function setFirstName(?string $firstName): self
    {
        $this->firstName = (string) $firstName;

        return $this;
    }

    public function setFullnameCanonical(?string $fullnameCanonical): self
    {
        $this->fullnameCanonical = $fullnameCanonical;

        return $this;
    }

    public function setGender(?Gender $gender): self
    {
        $this->gender = $gender;

        return $this;
    }

    public function setEmploymentStatus(?EmploymentStatus $employmentStatus): self
    {
        $this->employmentStatus = $employmentStatus;

        return $this;
    }

    public function setGenderComment(CommentEmbeddable $genderComment): self
    {
        $this->genderComment = $genderComment;

        return $this;
    }

    public function setLastName(?string $lastName): self
    {
        $this->lastName = (string) $lastName;

        return $this;
    }

    public function setMaritalStatus(?MaritalStatus $maritalStatus = null): self
    {
        $this->maritalStatus = $maritalStatus;

        return $this;
    }

    public function setMaritalStatusComment(CommentEmbeddable $maritalStatusComment): self
    {
        $this->maritalStatusComment = $maritalStatusComment;

        return $this;
    }

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

        return $this;
    }

    public function setMemo(?string $memo): self
    {
        if (null === $memo) {
            $memo = '';
        }

        if ($this->memo !== $memo) {
            $this->memo = $memo;
        }

        return $this;
    }

    public function setMobilenumber(?PhoneNumber $mobilenumber): self
    {
        $this->mobilenumber = $mobilenumber;

        return $this;
    }

    public function setNationality(?Country $nationality = null): self
    {
        $this->nationality = $nationality;

        return $this;
    }

    public function setNumberOfChildren(?int $numberOfChildren): self
    {
        $this->numberOfChildren = $numberOfChildren;

        return $this;
    }

    public function setOtherPhoneNumbers(Collection $otherPhoneNumbers): self
    {
        $this->otherPhoneNumbers = $otherPhoneNumbers;

        return $this;
    }

    public function setPhonenumber(?PhoneNumber $phonenumber): self
    {
        $this->phonenumber = $phonenumber;

        return $this;
    }

    public function setPlaceOfBirth(?string $placeOfBirth): self
    {
        if (null === $placeOfBirth) {
            $placeOfBirth = '';
        }

        $this->placeOfBirth = $placeOfBirth;

        return $this;
    }

    /**
     * @param (Collection<int, Language>) $spokenLanguages
     */
    public function setSpokenLanguages(Collection $spokenLanguages): self
    {
        $this->spokenLanguages = $spokenLanguages;

        return $this;
    }

    public function setUpdatedAt(\DateTimeInterface $datetime): self
    {
        $this->updatedAt = $datetime;

        return $this;
    }

    public function setUpdatedBy(User $user): self
    {
        $this->updatedBy = $user;

        return $this;
    }

    private function getCurrentCenterHistory(): ?PersonCenterHistory
    {
        if (0 === $this->centerHistory->count()) {
            return null;
        }

        $criteria = Criteria::create();
        $now = new \DateTimeImmutable('now');
        $criteria->where(Criteria::expr()->lte('startDate', $now))
            ->andWhere(Criteria::expr()->orX(
                Criteria::expr()->isNull('endDate'),
                Criteria::expr()->gt('endDate', $now)
            ));

        $histories = $this->centerHistory->matching($criteria);

        return match ($histories->count()) {
            0 => null,
            1 => $histories->first(),
            default => throw new \UnexpectedValueException('It should not contains more than one center at a time'),
        };
    }

    /**
     * This private function scan accompanyingPeriodParticipations Collection,
     * searching for a given AccompanyingPeriod.
     */
    private function participationsContainAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): ?AccompanyingPeriodParticipation
    {
        foreach ($this->accompanyingPeriodParticipations as $participation) {
            /** @var AccompanyingPeriodParticipation $participation */
            if ($participation->getAccompanyingPeriod() === $accompanyingPeriod) {
                return $participation;
            }
        }

        return null;
    }
}
