<?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\MainBundle\Entity\User;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Serializer\Normalizer\UserNormalizer;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Workflow\Registry;

class AccompanyingPeriodWorkNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface
{
    use NormalizerAwareTrait;

    private const IGNORE_WORK = 'ignore:work';

    public function __construct(private readonly Registry $registry, private readonly EntityWorkflowRepository $entityWorkflowRepository, private readonly MetadataExtractor $metadataExtractor) {}

    public function normalize($object, ?string $format = null, array $context = []): array|\ArrayObject|bool|float|int|string|null
    {
        if (!$object instanceof AccompanyingPeriodWork && 'json' === $format) {
            throw new UnexpectedValueException('Object cannot be null or empty when format is json');
        }
        if ('docgen' === $format && !($object instanceof AccompanyingPeriodWork || null === $object)) {
            throw new UnexpectedValueException(sprintf('Object must be an instanceof AccompanyingPeriodWork or null when format is docgen, %s given', get_debug_type($object)));
        }

        $cleanContext = array_filter($context, fn (string|int $key) => !in_array($key, ['docgen:expects', self::IGNORE_WORK], true), ARRAY_FILTER_USE_KEY);

        if (null === $object && 'docgen' === $format) {
            $dateNull = $this->normalizer->normalize(null, $format, [...$context, 'docgen:expects' => \DateTimeImmutable::class]);
            $userNull = $this->normalizer->normalize(null, $format, [...$context, 'docgen:expects' => User::class]);

            return [
                'isNull' => true,
                'type' => 'accompanying_period_work',
                'accompanyingPeriodWorkEvaluations' => [],
                'referrers' => [],
                'createdAt' => $dateNull,
                'createdAutomatically' => 'false',
                'createdAutomaticallyReason' => '',
                'createdBy' => $userNull,
                'endDate' => $dateNull,
                'goals' => [],
                'handlingThierParty' => $this->normalizer->normalize(null, $format, [...$cleanContext, 'docgen:expects' => ThirdParty::class]),
                'id' => '',
                'note' => '',
                'persons' => [],
                'results' => [],
                'socialAction' => $this->normalizer->normalize(null, $format, [...$cleanContext, 'docgen:expects' => SocialAction::class]),
                'startDate' => $dateNull,
                'thirdParties' => [],
                'updatedAt' => $dateNull,
                'updatedBy' => $userNull,
            ];
        }

        $initial = $this->normalizer->normalize($object, $format, array_merge(
            $cleanContext,
            [self::IGNORE_WORK => null === $object ? null : spl_object_hash($object)]
        ));

        // due to bug: https://api-platform.com/docs/core/serialization/#collection-relation
        // and also: https://github.com/symfony/symfony/issues/36965
        // we have to rewrite the evaluations as a collection
        $initial['accompanyingPeriodWorkEvaluations'] = $this->normalizer->normalize(
            $object->getAccompanyingPeriodWorkEvaluations()->getValues(),
            $format,
            [...$cleanContext]
        );

        // add the referrers
        $initial['referrers'] = [];

        foreach ($object->getReferrersHistory() as $referrerHistory) {
            if (null !== $referrerHistory->getEndDate()) {
                continue;
            }

            $initial['referrers'][] = $this->normalizer->normalize(
                $referrerHistory->getUser(),
                $format,
                [...$cleanContext, UserNormalizer::AT_DATE => $referrerHistory->getStartDate()]
            );
        }

        if ('json' === $format) {
            // then, we add normalization for things which are not into the entity
            $initial['workflows_availables'] = $this->metadataExtractor->availableWorkflowFor(
                AccompanyingPeriodWork::class,
                $object->getId()
            );

            $initial['workflows_availables_evaluation'] = $this->metadataExtractor->availableWorkflowFor(
                AccompanyingPeriodWorkEvaluation::class
            );

            $initial['workflows_availables_evaluation_documents'] = $this->metadataExtractor->availableWorkflowFor(
                AccompanyingPeriodWorkEvaluationDocument::class
            );

            $workflows = $this->entityWorkflowRepository->findBy([
                'relatedEntityClass' => AccompanyingPeriodWork::class,
                'relatedEntityId' => $object->getId(),
            ]);

            $initial['workflows'] = $this->normalizer->normalize($workflows, 'json', $context);
        }

        return $initial;
    }

    public function supportsNormalization($data, ?string $format = null, array $context = []): bool
    {
        return match ($format) {
            'json' => $data instanceof AccompanyingPeriodWork && ($context[self::IGNORE_WORK] ?? null) !== spl_object_hash($data),
            'docgen' => ($data instanceof AccompanyingPeriodWork || (null === $data && ($context['docgen:expects'] ?? null) === AccompanyingPeriodWork::class))
                && !array_key_exists(self::IGNORE_WORK, $context),
            default => false,
        };
    }
}
