<?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\Serializer\Normalizer;

use Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Chill\MainBundle\Serializer\Normalizer\UserNormalizer;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Templating\Entity\ClosingMotiveRender;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Contracts\Translation\TranslatorInterface;

class AccompanyingPeriodDocGenNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface
{
    use NormalizerAwareTrait;

    private const IGNORE_FIRST_PASS_KEY = 'acc_period_ignore_first_pass';

    private const PERIOD_NULL = [
        'id',
        'closingDate' => \DateTime::class,
        'closingMotive' => AccompanyingPeriod\ClosingMotive::class,
        'confidential',
        'confidentialText',
        'createdAt' => \DateTime::class,
        'createdBy' => User::class,
        'emergency',
        'emergencyText',
        'openingDate' => \DateTime::class,
        'origin' => AccompanyingPeriod\Origin::class,
        'originText',
        'requestorAnonymous',
        'socialIssues',
        'intensity',
        'step',
        'closingMotiveText',
        'socialIssuesText',
        'scopes' => Collection::class,
        'scopesText',
        'ref' => User::class,
        'participations' => Collection::class,
        'currentParticipations' => Collection::class,
        'requestorPerson' => Person::class,
        'requestorThirdParty' => ThirdParty::class,
        'resources' => Collection::class,
        'location' => Address::class,
        'locationPerson' => Person::class,
        'administrativeLocation' => Location::class,
        'pinnedComment' => AccompanyingPeriod\Comment::class,
    ];

    public function __construct(private readonly TranslatorInterface $translator, private readonly TranslatableStringHelper $translatableStringHelper, private readonly SocialIssueRender $socialIssueRender, private readonly ClosingMotiveRender $closingMotiveRender, private readonly ScopeResolverDispatcher $scopeResolverDispatcher) {}

    /**
     * @param AccompanyingPeriod|null $period
     * @param string|null             $format
     */
    public function normalize($period, $format = null, array $context = [])
    {
        if ($period instanceof AccompanyingPeriod) {
            $scopes = $this->scopeResolverDispatcher->isConcerned($period) ? $this->scopeResolverDispatcher->resolveScope($period) : [];
            $userHistory = $period->getCurrentUserHistory();

            if (!\is_array($scopes)) {
                $scopes = [$scopes];
            }

            $addressContext = array_merge($context, ['docgen:expects' => Address::class, 'groups' => 'docgen:read']);
            $dateContext = array_merge($context, ['docgen:expects' => \DateTime::class, 'groups' => 'docgen:read']);
            $userContext = array_merge($context, ['docgen:expects' => User::class, 'groups' => 'docgen:read']);
            $participationContext = array_merge($context, ['docgen:expects' => AccompanyingPeriodParticipation::class, 'groups' => 'docgen:read']);
            $administrativeLocationContext = array_merge($context, ['docgen:expects' => Location::class, 'groups' => 'docgen:read']);
            $workContext = array_merge($context, ['docgen:expects' => AccompanyingPeriod\AccompanyingPeriodWork::class, 'groups' => 'docgen:read']);

            return [
                'id' => $period->getId(),
                'type' => 'accompanying_period',
                'isNull' => false,
                'closingDate' => $this->normalizer->normalize($period->getClosingDate(), $format, $dateContext),
                'closingMotive' => $this->normalizer->normalize($period->getClosingMotive(), $format, array_merge($context, ['docgen:expects' => AccompanyingPeriod\ClosingMotive::class])),
                'confidential' => $period->isConfidential(),
                'createdAt' => $this->normalizer->normalize($period->getCreatedAt(), $format, $dateContext),
                'createdBy' => $this->normalizer->normalize($period->getCreatedBy(), $format, [...$userContext, UserNormalizer::AT_DATE => $period->getCreatedAt()]),
                'emergency' => $period->isEmergency(),
                'openingDate' => $this->normalizer->normalize($period->getOpeningDate(), $format, $dateContext),
                'origin' => $this->normalizer->normalize($period->getOrigin(), $format, array_merge($context, ['docgen:expects' => AccompanyingPeriod\Origin::class])),
                'participations' => $this->normalizer->normalize($period->getParticipations(), $format, $participationContext),
                'currentParticipations' => $this->normalizer->normalize($period->getCurrentParticipations(), $format, $participationContext),
                'requestorAnonymous' => $period->isRequestorAnonymous(),
                'requestorPerson' => $this->normalizer->normalize($period->getRequestorPerson(), $format, array_merge($context, ['docgen:expects' => Person::class])),
                'hasRequestorPerson' => null !== $period->getRequestorPerson(),
                'requestorThirdParty' => $this->normalizer->normalize($period->getRequestorThirdParty(), $format, array_merge($context, ['docgen:expects' => ThirdParty::class])),
                'hasRequestorThirdParty' => null !== $period->getRequestorThirdParty(),
                'resources' => $this->normalizer->normalize($period->getResources(), $format, $context),
                'scopes' => $this->normalizer->normalize($scopes, $format, array_merge($context, ['docgen:expects' => Scope::class, 'groups' => 'docgen:read'])),
                'socialIssues' => $this->normalizer->normalize($period->getSocialIssues(), $format, $context),
                'intensity' => $this->translator->trans($period->getIntensity()),
                'step' => $this->translator->trans('accompanying_period.'.$period->getStep()),
                'emergencyText' => $period->isEmergency() ? $this->translator->trans('accompanying_period.emergency') : '',
                'confidentialText' => $period->isConfidential() ? $this->translator->trans('confidential') : '',
                'originText' => null !== $period->getOrigin() ? $this->translatableStringHelper->localize($period->getOrigin()->getLabel()) : '',
                'isClosed' => null !== $period->getClosingDate(),
                'closingMotiveText' => null !== $period->getClosingMotive() ?
                    $this->closingMotiveRender->renderString($period->getClosingMotive(), []) : '',
                'ref' => $this->normalizer->normalize($userHistory?->getUser(), $format, [...$userContext, UserNormalizer::AT_DATE => $userHistory?->getStartDate()]),
                'hasRef' => null !== $period->getUser(),
                'socialIssuesText' => implode(', ', array_map(fn (SocialIssue $s) => $this->socialIssueRender->renderString($s, []), $period->getSocialIssues()->toArray())),
                'scopesText' => implode(', ', array_map(fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), $scopes)),
                'hasRequestor' => null !== $period->getRequestor(),
                'requestorKind' => $period->getRequestorKind(),
                'hasLocation' => null !== $period->getLocation(),
                'hasLocationPerson' => null !== $period->getPersonLocation(),
                'hasAdministrativeLocation' => null !== $period->getAdministrativeLocation(),
                'locationPerson' => $this->normalizer->normalize($period->getPersonLocation(), $format, array_merge($context, ['docgen:expects' => Person::class])),
                'location' => $this->normalizer->normalize($period->getLocation(), $format, $addressContext),
                'administrativeLocation' => $this->normalizer->normalize($period->getAdministrativeLocation(), $format, $administrativeLocationContext),
                'works' => $this->normalizer->normalize($period->getWorks()->getValues(), $format, $workContext),
                'comments' => $this->normalizer->normalize($period->getComments(), $format, array_merge($context, ['docgen:expects' => AccompanyingPeriod\Comment::class])),
                'pinnedComment' => $this->normalizer->normalize($period->getPinnedComment(), $format, array_merge($context, ['docgen:expects' => AccompanyingPeriod\Comment::class])),
            ];
        }

        if (null === $period) {
            return array_merge(
                (new NormalizeNullValueHelper($this->normalizer, 'type', 'accompanying_period'))
                    ->normalize(self::PERIOD_NULL, $format, $context),
                [
                    'hasRef' => false,
                    'requestorKind' => 'none',
                    'hasRequestor' => false,
                    'hasRequestorPerson' => false,
                    'hasRequestorThirdParty' => false,
                    'isClosed' => false,
                    'confidential' => false,
                    'hasLocation' => false,
                    'hasLocationPerson' => false,
                    'hasAdministrativeLocation' => false,
                    'works' => [],
                    'comments' => [],
                ]
            );
        }

        throw new InvalidArgumentException('This neither an accompanying period or null.');
    }

    public function supportsNormalization($data, $format = null, array $context = []): bool
    {
        if ('docgen' !== $format) {
            return false;
        }

        if ($data instanceof AccompanyingPeriod) {
            return true;
        }

        if (null === $data && AccompanyingPeriod::class === ($context['docgen:expects'] ?? null)) {
            return true;
        }

        return false;
    }
}
