<?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\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;

/**
 * This denormalizer rely on AbstractNormalizer for most of the job, and
 * add some logic for synchronizing collection.
 */
class AccompanyingPeriodWorkDenormalizer implements ContextAwareDenormalizerInterface, DenormalizerAwareInterface
{
    use DenormalizerAwareTrait;

    use ObjectToPopulateTrait;

    final public const GROUP_CREATE = 'accompanying_period_work:create';

    final public const GROUP_EDIT = 'accompanying_period_work:edit';

    public function __construct(private readonly AccompanyingPeriodWorkRepository $workRepository, private readonly EntityManagerInterface $em) {}

    public function denormalize($data, $type, $format = null, array $context = [])
    {
        $work = $this->denormalizer->denormalize($data, $type, $format, \array_merge(
            $context,
            ['skip' => self::class]
        ));

        if (\in_array('accompanying_period_work:edit', $context['groups'] ?? [], true)) {
            $this->handleEvaluationCollection($data, $work, $format, $context);
        }

        return $work;
    }

    public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
    {
        return AccompanyingPeriodWork::class === $type
            && self::class !== ($context['skip'] ?? null)
            && \is_array($data)
            && \array_key_exists('type', $data)
            && 'accompanying_period_work' === $data['type'];
    }

    private function handleEvaluationCollection(array $data, AccompanyingPeriodWork $work, string $format, array $context)
    {
        $dataById = [];
        $dataWithoutId = [];

        foreach ($data['accompanyingPeriodWorkEvaluations'] as $e) {
            if (\array_key_exists('id', $e)) {
                $dataById[$e['id']] = $e;
            } else {
                $dataWithoutId[] = $e;
            }
        }

        // partition the separate kept evaluations and removed one
        [$kept, $removed] = $work->getAccompanyingPeriodWorkEvaluations()
            ->partition(
                static fn (int $key, AccompanyingPeriodWorkEvaluation $a) => \array_key_exists($a->getId(), $dataById)
            );

        // remove the evaluations from work
        foreach ($removed as $r) {
            $work->removeAccompanyingPeriodWorkEvaluation($r);
        }
        // handle the evaluation kept
        foreach ($kept as $k) {
            $this->denormalizer->denormalize(
                $dataById[$k->getId()],
                AccompanyingPeriodWorkEvaluation::class,
                $format,
                \array_merge(
                    $context,
                    [
                        'groups' => ['write'],
                        AbstractNormalizer::OBJECT_TO_POPULATE => $k,
                    ]
                )
            );
        }
        // create new evaluation
        foreach ($dataWithoutId as $newData) {
            $evaluation = $this->denormalizer->denormalize(
                $newData,
                AccompanyingPeriodWorkEvaluation::class,
                $format,
                \array_merge(
                    $context,
                    ['groups' => ['accompanying_period_work_evaluation:create']]
                )
            );
            $work->addAccompanyingPeriodWorkEvaluation($evaluation);
        }
    }
}
