<?php

declare(strict_types=1);

/*
 * @author TRYDEA - 2024
 */

namespace Shared\TransferSync\Service;

use Mobile\Device\Entity\Block;
use Mobile\Device\Entity\BusinessObject;
use Mobile\Device\Entity\Device;
use Mobile\Device\Entity\EntryNote;
use Mobile\Device\Entity\Factor;
use Mobile\Device\Entity\Individual;
use Mobile\Device\Entity\Modality;
use Mobile\Device\Entity\OutExperimentationZone;
use Mobile\Device\Entity\Protocol;
use Mobile\Device\Entity\SubBlock;
use Mobile\Device\Entity\Treatment;
use Mobile\Device\Entity\UnitParcel;
use Mobile\Measure\Entity\Annotation;
use Mobile\Measure\Entity\FormField;
use Mobile\Measure\Entity\GeneratedField;
use Mobile\Measure\Entity\Measure;
use Mobile\Measure\Entity\Session;
use Mobile\Measure\Entity\Variable\Base\Variable;
use Mobile\Measure\Entity\Variable\GeneratorVariable;
use Mobile\Measure\Entity\Variable\Mark;
use Mobile\Measure\Entity\Variable\Scale;
use Mobile\Measure\Entity\Variable\StateCode;
use Mobile\Measure\Entity\Variable\UniqueVariable;
use Mobile\Measure\Entity\Variable\ValueHintList;
use Mobile\Project\Entity\DataEntryProject;
use Mobile\Project\Entity\NatureZHE;
use Mobile\Project\Entity\Platform;
use Shared\Authentication\Entity\Site;
use Shared\Authentication\Entity\User;
use Shared\TransferSync\Entity\IndividualStructureChange;
use Symfony\Component\CssSelector\Exception\InternalErrorException;
use Webapp\Core\Entity\AbstractVariable;
use Webapp\Core\Entity\Annotation as WebappAnnotation;
use Webapp\Core\Entity\Block as WebappBlock;
use Webapp\Core\Entity\ChangeReport;
use Webapp\Core\Entity\Experiment as WebappExperiment;
use Webapp\Core\Entity\Factor as WebappFactor;
use Webapp\Core\Entity\FieldGeneration as WebappFieldGeneration;
use Webapp\Core\Entity\FieldMeasure as WebappFieldMeasure;
use Webapp\Core\Entity\GeneratorVariable as WebappGeneratorVariable;
use Webapp\Core\Entity\Individual as WebappIndividual;
use Webapp\Core\Entity\Measure as WebappMeasure;
use Webapp\Core\Entity\Modality as WebappModality;
use Webapp\Core\Entity\Note;
use Webapp\Core\Entity\OezNature;
use Webapp\Core\Entity\OutExperimentationZone as WebappOutExperimentationZone;
use Webapp\Core\Entity\Platform as WebappPlatform;
use Webapp\Core\Entity\Project as WebappProject;
use Webapp\Core\Entity\ProjectData;
use Webapp\Core\Entity\Protocol as WebappProtocol;
use Webapp\Core\Entity\Session as WebappSession;
use Webapp\Core\Entity\SimpleVariable;
use Webapp\Core\Entity\StateCode as WebappStateCode;
use Webapp\Core\Entity\SubBlock as WebappSubBlock;
use Webapp\Core\Entity\SurfacicUnitPlot;
use Webapp\Core\Entity\Treatment as WebappTreatment;
use Webapp\Core\Entity\UnitPlot;
use Webapp\Core\Entity\UnitPlot as WebappUnitPlot;
use Webapp\Core\Entity\ValueList;
use Webapp\Core\Entity\ValueListItem;
use Webapp\Core\Entity\VariableScale;
use Webapp\Core\Entity\VariableScaleItem;
use Webapp\Core\Enumeration\ChangeTypeEnum;
use Webapp\Core\Enumeration\ExperimentStateEnum;
use Webapp\Core\Enumeration\PathLevelEnum;
use Webapp\Core\Enumeration\SpreadingEnum;
use Webapp\Core\Enumeration\VariableFormatEnum;
use Webapp\Core\Enumeration\VariableTypeEnum;

/**
 * Class MobileToWebappBridge
 * Service to allow transfer of Mobile Project into Webapp Project.
 */
class MobileToWebappBridge implements MobileToWebappBridgeInterface
{
    /**
     * @param Session[] $mobileSessions
     *
     * @throws InternalErrorException
     */
    public function buildDataEntryFromMobileToWebapp(WebappProject $project, array $mobileSessions, array $changes, array $objectsAnnotations, array $mobileNotes, User $user): ProjectData
    {
        $projectData = (new ProjectData())
            ->setName($project->getName())
            ->setUser($user);

        $variableMap = [];
        $stateMap = $this->buildWebappStateCodeMap($project);
        $measurableMap = $this->buildWebappMeasurableMap($project);

        foreach ($mobileSessions as $mobileSession) {
            $this->handleProjectDataSession($mobileSession, $projectData, $measurableMap, $variableMap, $stateMap, $user);
        }

        /** @var WebappGeneratorVariable|SimpleVariable $variable */
        foreach ($variableMap as $variable) {
            if (null === $variable->getGeneratorVariable()) {
                /** @var AbstractVariable|null $projectVariable */
                $projectVariable = array_values(array_filter($project->getVariables(), fn (AbstractVariable $pvariable) => $pvariable->getName() === $variable->getName()))[0] ?? null;
                if (null !== $projectVariable) {
                    $variable->setOpenSilexInstance($projectVariable->getOpenSilexInstance());
                    $variable->setOpenSilexUri($projectVariable->getOpenSilexUri());
                }
                if ($variable instanceof WebappGeneratorVariable) {
                    $projectData->addGeneratorVariable($variable);
                } else {
                    $projectData->addSimpleVariable($variable);
                } // TODO atm Semi auto variables are transformed into Simple variable bc no need to reuse device
            }
        }

        $lastSession = null;
        foreach ($projectData->getSessions() as $session) {
            if (null === $lastSession || $session->getStartedAt() > $lastSession->getStartedAt()) {
                $lastSession = $session;
            }
        }

        foreach ($objectsAnnotations as $objectsAnnotation) {
            $this->handleAnnotation($objectsAnnotation, $lastSession, $measurableMap, null);
        }

        foreach ($changes as $change) {
            $this->handleChange($change, $projectData, $measurableMap);
        }

        foreach ($mobileNotes as $mobileNote) {
            $this->handleNote($mobileNote, $measurableMap);
        }
        $project->addProjectData($projectData);

        return $projectData;
    }

    /**
     * @throws InternalErrorException
     */
    private function handleAnnotation(Annotation $annotation, WebappSession $session, ?array $measurableMap, ?WebappMeasure $webappMeasure)
    {
        $webappAnnotation = new WebappAnnotation();
        $webappAnnotation->setName($annotation->getName())
            ->setType($annotation->getType())
            ->setValue($annotation->getValue())
            ->setCategories($annotation->getCategories())
            ->setImage($annotation->getImage())
            ->setKeywords($annotation->getKeywords())
            ->setTimestamp($annotation->getTimestamp());
        $session->addAnnotation($webappAnnotation);

        if (null !== $measurableMap) {
            $webappAnnotation->setTarget($measurableMap[$this->getWebappMobileUri($annotation->getProjectObject())]);
        } else {
            $webappAnnotation->setTarget($webappMeasure);
        }
    }

    /**
     * @throws InternalErrorException
     */
    private function handleProjectDataSession(Session $mobileSession, ProjectData $projectData, array $measurableMap, array &$variableMap, array $stateMap, User $user): void
    {
        $webappSession = (new WebappSession())
            ->setEndedAt($mobileSession->getEndedAt())
            ->setStartedAt($mobileSession->getStartedAt())
            ->setUser($user);

        /** @var $mobileFormField FormField */
        foreach ($mobileSession->getFormFields() as $mobileFormField) {
            $this->handleSessionFieldMeasure($mobileFormField, $webappSession, $measurableMap, $variableMap, $stateMap);
        }

        $projectData->addSession($webappSession);
    }

    /**
     * @throws InternalErrorException
     */
    private function createWebappFieldMeasure(FormField $mobileFormField, array $measurableMap, array &$variableMap): WebappFieldMeasure
    {
        if (!isset($variableMap[$mobileFormField->getVariable()->getUri()])) {
            $mobileVariable = $mobileFormField->getVariable();
            $webappVariable = $this->constructMissingVariable($mobileVariable, $variableMap);
            $variableMap[$mobileFormField->getVariable()->getUri()] = $webappVariable;
        }

        return (new WebappFieldMeasure())
            ->setTarget($measurableMap[$this->getWebappMobileUri($mobileFormField->getTarget())])
            ->setVariable($variableMap[$mobileFormField->getVariable()->getUri()]);
    }

    /**
     * @return WebappGeneratorVariable|SimpleVariable
     */
    public function constructMissingVariable(Variable $mobileVariable, array &$variableMap)
    {
        if (null !== $mobileVariable->getGeneratorVariable() && isset($variableMap[$mobileVariable->getGeneratorVariable()->getUri()])) {
            /** @var WebappGeneratorVariable $parentVariable */
            $parentVariable = $variableMap[$mobileVariable->getGeneratorVariable()->getUri()];

            return array_values(array_filter($parentVariable->getGeneratedVariables(), function (AbstractVariable $item) use ($mobileVariable) {
                return $item->getName() === $mobileVariable->getName();
            }))[0];
        }
        if ($mobileVariable instanceof GeneratorVariable) {
            $webappVariable = new WebappGeneratorVariable('', '', 0, '', false, '', false, false, false);
            $webappVariable->setDirectCounting(true)
                ->setGeneratedPrefix($mobileVariable->getGeneratedPrefix())
                ->setNumeralIncrement($mobileVariable->isNumeralIncrement())
                ->setPathWayHorizontal(true);
            foreach ($mobileVariable->getGeneratorVariables() as $generatedVariable) {
                $webappVariable->addGeneratedGeneratorVariable($this->constructMissingVariable($generatedVariable, $variableMap));
            }
            foreach ($mobileVariable->getUniqueVariables() as $generatedVariable) {
                $webappVariable->addGeneratedSimpleVariable($this->constructMissingVariable($generatedVariable, $variableMap));
            }
        } elseif ($mobileVariable instanceof UniqueVariable) {
            $webappVariable = new SimpleVariable('', '', 0, '', false, '');
            $format = null;
            $webappVariable->setType($mobileVariable->getType());
            switch ($mobileVariable->getType()) {
                case VariableTypeEnum::REAL:
                    $format = null === $mobileVariable->getFormat() ? null : VariableFormatEnum::DECIMAL_NUMBER;
                    break;
                case VariableTypeEnum::ALPHANUMERIC:
                    $format = null === $mobileVariable->getFormat() ? null : VariableFormatEnum::CHARACTER_NUMBER;
                    break;
                case VariableTypeEnum::DATE:
                    $format = $mobileVariable->getFormat();
                    break;
            }
            $webappVariable->setFormat($format);
            if ((bool) $mobileVariable->getValueHintList()) {
                $this->handleValueList($mobileVariable->getValueHintList(), $webappVariable);
            }
            if ((bool) $mobileVariable->getScale()) {
                $this->handleScale($mobileVariable->getScale(), $webappVariable);
            }
        }
        $webappVariable->setName($mobileVariable->getName())
            ->setRepetitions($mobileVariable->getRepetitions())
            ->setShortName($mobileVariable->getShortName())
            ->setOrder($mobileVariable->getOrder())
            ->setIdentifier(null)
            ->setComment($mobileVariable->getComment())
            ->setDefaultTrueValue('true' === $mobileVariable->getDefaultValue())
            ->setMandatory($mobileVariable->isMandatory())
            ->setUnit($mobileVariable->getUnit());

        $webappVariable->setPathLevel($mobileVariable->getPathLevel());

        return $webappVariable;
    }

    private function handleValueList(ValueHintList $hintList, SimpleVariable $variable): void
    {
        $valueList = (new ValueList())->setName($hintList->getName());
        foreach ($hintList->getValueHints() as $item) {
            $valueList->addValue(new ValueListItem($item->getValue()));
        }
        $variable->setValueList($valueList);
    }

    private function handleScale(Scale $scale, SimpleVariable $variable): void
    {
        $newScale = (new VariableScale())
            ->setName($scale->getName())
            ->setMinValue($scale->getMinValue())
            ->setMaxValue($scale->getMaxValue())
            ->setOpen(!$scale->isExclusive());
        /** @var Mark $mark */
        foreach ($scale->getMarks() as $mark) {
            $newScale->addValue((new VariableScaleItem($mark->getValue()))
                ->setText($mark->getText())
                ->setPic('' === $mark->getImage() ? null : $mark->getImage()));
        }
        $variable->setScale($newScale);
    }

    /**
     * @throws InternalErrorException
     */
    private function handleFieldMeasureGeneration(GeneratedField $mobileGenField, WebappFieldMeasure $webappFieldMeasure, array $measurableMap, array &$variableMap, array $stateMap, WebappSession $webappSession): void
    {
        $webappFieldGeneration = (new WebappFieldGeneration())
            ->setNumeralIncrement($mobileGenField->isNumeralIncrement())
            ->setIndex($mobileGenField->getIndex())
            ->setPrefix($mobileGenField->getPrefix());

        foreach ($mobileGenField->getFormFields() as $mobileChild) {
            $webappChild = $this->createWebappFieldMeasureRecursively($mobileChild, $measurableMap, $variableMap, $stateMap, $webappSession);

            $webappFieldGeneration->addChild($webappChild);
        }

        $webappFieldMeasure->addFieldGeneration($webappFieldGeneration);
    }

    /**
     * @throws InternalErrorException
     */
    private function createWebappFieldMeasureRecursively(FormField $mobileFormField, array $measurableMap, array &$variableMap, array $stateMap, WebappSession $webappSession): WebappFieldMeasure
    {
        $webappFieldMeasure = $this->createWebappFieldMeasure($mobileFormField, $measurableMap, $variableMap);

        /** @var $mobileMeasure Measure */
        foreach ($mobileFormField->getMeasures() as $mobileMeasure) {
            $this->handleFieldMeasureMeasure($mobileMeasure, $webappFieldMeasure, $stateMap, $webappSession);
        }

        /** @var $mobileGenField GeneratedField */
        foreach ($mobileFormField->getGeneratedFields() as $mobileGenField) {
            $this->handleFieldMeasureGeneration($mobileGenField, $webappFieldMeasure, $measurableMap, $variableMap, $stateMap, $webappSession);
        }

        return $webappFieldMeasure;
    }

    /**
     * @throws InternalErrorException
     */
    private function handleSessionFieldMeasure(FormField $mobileFormField, WebappSession $webappSession, array $measurableMap, array &$variableMap, array $stateMap): void
    {
        $webappFieldMeasure = $this->createWebappFieldMeasureRecursively($mobileFormField, $measurableMap, $variableMap, $stateMap, $webappSession);

        $webappSession->addFieldMeasure($webappFieldMeasure);
    }

    /**
     * @throws InternalErrorException
     */
    private function handleFieldMeasureMeasure(Measure $mobileMeasure, WebappFieldMeasure $webappFieldMeasure, array $stateMap, WebappSession $webappSession): void
    {
        $webappMeasure = (new WebappMeasure())
            ->setValue($mobileMeasure->getValue())
            ->setRepetition($mobileMeasure->getRepetition())
            ->setTimestamp($mobileMeasure->getTimestamp())
            ->setState(null !== $mobileMeasure->getState()
                ? $stateMap[$mobileMeasure->getState()->getCode()]
                : null);

        foreach ($mobileMeasure->getAnnotations() as $annotation) {
            $this->handleAnnotation($annotation, $webappSession, null, $webappMeasure);
        }

        $webappFieldMeasure->addMeasure($webappMeasure);
    }

    private function buildWebappStateCodeMap(WebappProject $project): array
    {
        $stateMap = [];
        foreach ($project->getStateCodes() as $stateCode) {
            $stateMap[$stateCode->getCode()] = $stateCode;
        }

        return $stateMap;
    }

    /**
     * @throws InternalErrorException
     */
    private function handleChange(IndividualStructureChange $change, ProjectData $projectData, array $measurableMap)
    {
        $report = new ChangeReport();

        /** @var UnitParcel|Individual $mobileTarget */
        $mobileTarget = $change->getRequestIndividual() ?? $change->getRequestUnitParcel();
        $mobileTargetResponse = $change->getResponseIndividual() ?? $change->getResponseUnitParcel();

        /** @var WebappIndividual|SurfacicUnitPlot $target */
        $target = $measurableMap[$this->getWebappMobileUri($mobileTarget)];

        if (null === $mobileTarget->getStateCode()) {
            if (null === $mobileTargetResponse->getStateCode()) {
                throw new InternalErrorException('Demise date hasn\'t changed');
            }
            $report->setChangeType(ChangeTypeEnum::DEAD)
                ->setLastChangeDate($mobileTargetResponse->getApparitionDate())
                ->setChangeDate($mobileTargetResponse->getDemiseDate());
        } else {
            if (null !== $mobileTargetResponse->getStateCode()) {
                throw new InternalErrorException('Apparition date hasn\'t changed');
            }
            $report->setChangeType(ChangeTypeEnum::REPLANTED)
                ->setLastChangeDate($mobileTargetResponse->getDemiseDate() ?? new \DateTime())
                ->setChangeDate($mobileTargetResponse->getApparitionDate());
        }

        $report->setAproved($change->isApproved());
        if (null === $change->getRequestIndividual()) {
            $report->setInvolvedSurfacicUnitPlot($target);
        } else {
            $report->setInvolvedIndividual($target);
        }

        if ($change->isApproved()) {
            $target->setDead(ChangeTypeEnum::DEAD === $report->getChangeType());
            if ($target->isDead()) {
                $target->setDisappeared($report->getChangeDate());
            } else {
                $target->setAppeared($report->getChangeDate());
            }
        }

        $projectData->addChangeReport($report);
    }

    /**
     * @throws InternalErrorException
     */
    private function handleNote(EntryNote $mobileNote, $measurableMap)
    {
        /** @var WebappUnitPlot|WebappSubBlock|WebappBlock|WebappExperiment|WebappIndividual $webappObject */
        $webappObject = $measurableMap[$this->getWebappMobileUri($mobileNote->getBusinessObject())];
        /** @var Note $existingNote */
        foreach ($webappObject->getNotes() as $existingNote) {
            if ($existingNote->getCreator() === $mobileNote->getCreator()
                && $existingNote->getCreationDate() === $mobileNote->getCreationDate()
                && $measurableMap[$this->getWebappMobileUri($existingNote->getTarget())] === $webappObject
            ) {
                return;
            }
        }

        $note = new Note();
        $note->setDeleted($mobileNote->isDeleted())
            ->setText($mobileNote->getText())
            ->setCreationDate($mobileNote->getCreationDate())
            ->setCreator($mobileNote->getCreator());
        $webappObject->addNote($note);
    }

    /**
     * @throws InternalErrorException
     */
    private function buildWebappMeasurableMap(WebappProject $project): array
    {
        $objectMap = [];
        foreach ($project->getExperiments() as $experiment) {
            foreach ($experiment->getBlocks() as $block) {
                $parcelCallback = function ($unitPlotList) use (&$objectMap) {
                    foreach ($unitPlotList as $unitPlot) {
                        foreach ($unitPlot->getIndividuals() as $individual) {
                            $objectMap[$this->getWebappMobileUri($individual)] = $individual;
                        }
                        $objectMap[$this->getWebappMobileUri($unitPlot)] = $unitPlot;
                    }
                };
                $surfaceUnitPlotCallback = function ($unitPlotList) use (&$objectMap) {
                    foreach ($unitPlotList as $unitPlot) {
                        $objectMap[$this->getWebappMobileUri($unitPlot)] = $unitPlot;
                    }
                };
                foreach ($block->getSubBlocks() as $subBlock) {
                    $parcelCallback($subBlock->getUnitPlots());
                    $surfaceUnitPlotCallback($subBlock->getSurfacicUnitPlots());
                    $objectMap[$this->getWebappMobileUri($subBlock)] = $subBlock;
                }
                $parcelCallback($block->getUnitPlots());
                $surfaceUnitPlotCallback($block->getSurfacicUnitPlots());
                $objectMap[$this->getWebappMobileUri($block)] = $block;
            }
            $objectMap[$this->getWebappMobileUri($experiment)] = $experiment;
        }

        return $objectMap;
    }

    /**
     * @param BusinessObject|WebappUnitPlot|WebappSubBlock|WebappBlock|WebappExperiment|WebappIndividual $target
     *
     * @throws InternalErrorException
     */
    private function getWebappMobileUri($target): string
    {
        $uid = ($target instanceof Device || $target instanceof WebappExperiment) ? '' : $this->getWebappMobileUri($target->parent());
        if ($target instanceof Individual || $target instanceof WebappIndividual) {
            $uid .= '/'.PathLevelEnum::INDIVIDUAL;
        } elseif ($target instanceof UnitParcel || $target instanceof WebappUnitPlot || $target instanceof SurfacicUnitPlot) {
            $uid .= '/'.PathLevelEnum::UNIT_PLOT;
        } elseif ($target instanceof SubBlock || $target instanceof WebappSubBlock) {
            $uid .= '/'.PathLevelEnum::SUB_BLOCK;
        } elseif ($target instanceof Block || $target instanceof WebappBlock) {
            $uid .= '/'.PathLevelEnum::BLOCK;
        } elseif ($target instanceof Device || $target instanceof WebappExperiment) {
            $uid .= PathLevelEnum::EXPERIMENT;
        } else {
            throw new InternalErrorException('Unhandled BusinessObject in Uri conversion map.');
        }
        if ($target instanceof BusinessObject || $target instanceof WebappExperiment) {
            $uid .= $target->getName();
        } else {
            $uid .= $target->getNumber();
        }

        return $uid;
    }

    public function buildPlatformFromMobileToWebapp(Platform $platform, User $user, Site $site): WebappPlatform
    {
        $res = (new WebappPlatform())
            ->setName($platform->getName())
            ->setOwner($user)
            ->setPlaceName($platform->getPlace())
            ->setSiteName($platform->getSite());

        $platformUriMap = [];

        foreach ($platform->getDevices() as $experiment) {
            $res->addExperiment($this->buildExperiment($experiment, $user, $site, $platformUriMap));
        }

        return $res;
    }

    public function buildExperiment(Device $experiment, User $user, Site $site, array &$platformUriMap): WebappExperiment
    {
        $res = (new WebappExperiment())
            ->setOwner($user)
            ->setName($experiment->getName())
            ->setState(ExperimentStateEnum::LOCKED)
            ->setIndividualUP($experiment->isIndividualPU());

        $res->setProtocol($this->buildProtocol($experiment->getProtocol(), $user, $platformUriMap));

        foreach ($experiment->getBlocks() as $block) {
            $res->addBlocks($this->buildBlock($block, $site, $platformUriMap));
        }

        foreach ($experiment->getOutExperimentationZones() as $oez) {
            $res->addOutExperimentationZone($this->buildOutExperimentationZone($oez, $site, $platformUriMap));
        }

        return $res;
    }

    public function buildProtocol(Protocol $protocol, User $user, array &$platformUriMap): WebappProtocol
    {
        $res = (new WebappProtocol())
            ->setName($protocol->getName())
            ->setAim($protocol->getAim())
            ->setOwner($user);

        $factorCount = 0;
        foreach ($protocol->getFactors() as $factor) {
            $res->addFactors($this->buildFactor($factor, $platformUriMap)->setOrder($factorCount++));
        }

        foreach ($protocol->getTreatments() as $treatment) {
            $res->addTreatments($this->buildTreatment($treatment, $platformUriMap));
        }

        return $res;
    }

    public function buildFactor(Factor $factor, array &$platformUriMap): WebappFactor
    {
        $res = (new WebappFactor())
            ->setName($factor->getName());
        foreach ($factor->getModalities() as $modality) {
            $modality = $this->buildModality($modality, $platformUriMap);
            $res->addModality($modality);
        }

        return $res;
    }

    public function buildModality(Modality $modality, array &$platformUriMap): WebappModality
    {
        $res = (new WebappModality())
            ->setValue($modality->getValue());
        $platformUriMap[$modality->getUri()] = $res;

        return $res;
    }

    public function buildTreatment(Treatment $treatment, array &$platformUriMap): WebappTreatment
    {
        $res = (new WebappTreatment())
            ->setName($treatment->getName())
            ->setShortName($treatment->getShortName())
            ->setRepetitions($treatment->getRepetitions());
        foreach ($treatment->getModalities() as $modality) {
            $res->addModalities($platformUriMap[$modality->getUri()]);
        }
        $platformUriMap[$treatment->getUri()] = $res;

        return $res;
    }

    public function buildBlock(Block $block, Site $site, array &$platformUriMap): WebappBlock
    {
        $res = (new WebappBlock())
            ->setNumber($block->getName());

        foreach ($block->getSubBlocks() as $subBlock) {
            $res->addSubBlocks($this->buildSubBloc($subBlock, $site, $platformUriMap));
        }

        foreach ($block->getOutExperimentationZones() as $oez) {
            $res->addOutExperimentationZone($this->buildOutExperimentationZone($oez, $site, $platformUriMap));
        }

        $this->buildBlockOrSubBlockChildren($block, $site, $platformUriMap, $res);

        return $res;
    }

    public function buildSubBloc(SubBlock $subBlock, Site $site, array &$platformUriMap): WebappSubBlock
    {
        $res = (new WebappSubBlock())
            ->setNumber($subBlock->getName());

        foreach ($subBlock->getOutExperimentationZones() as $oez) {
            $res->addOutExperimentationZone($this->buildOutExperimentationZone($oez, $site, $platformUriMap));
        }

        $this->buildBlockOrSubBlockChildren($subBlock, $site, $platformUriMap, $res);

        return $res;
    }

    public function buildOutExperimentationZone(OutExperimentationZone $outExperimentationZoneXml, Site $site, array &$platformUriMap): WebappOutExperimentationZone
    {
        if (!isset($platformUriMap[$outExperimentationZoneXml->getNatureZHE()->getUri()])) {
            $this->buildOEZNature($outExperimentationZoneXml->getNatureZHE(), $site, $platformUriMap);
        }

        return (new WebappOutExperimentationZone())
            ->setX($outExperimentationZoneXml->getX())
            ->setY($outExperimentationZoneXml->getY())
            ->setNumber($outExperimentationZoneXml->getNumber())
            ->setNature($platformUriMap[$outExperimentationZoneXml->getNatureZHE()->getUri()]);
    }

    public function buildOEZNature(NatureZHE $oezNature, Site $site, array &$platformUriMap): OezNature
    {
        $existingOEZ = array_values(array_filter(
            $site->getOezNatures()->toArray(),
            fn (OezNature $existing) => $existing->getNature() === $oezNature->getName()
        ));
        $res = $existingOEZ[0] ?? (new OezNature())
            ->setSite($site)
            ->setNature($oezNature->getName())
            ->setColor($this->transformColor($oezNature->getColor()))
            ->setTexture($oezNature->getTexture());
        $platformUriMap[$oezNature->getUri()] = $res;

        return $res;
    }

    /**
     * @param Block|SubBlock             $block
     * @param WebappBlock|WebappSubBlock $webappBlock
     */
    private function buildBlockOrSubBlockChildren($block, Site $site, array &$platformUriMap, $webappBlock): void
    {
        $puCNT = 0;
        foreach ($block->getUnitParcels() as $unitPlot) {
            if (0 === $unitPlot->getIndividuals()->count()) {
                $res = $this->buildSurfacicUnitPlot($unitPlot, $platformUriMap);
                $webappBlock->addSurfacicUnitPlots($res);
            } else {
                $res = $this->buildUnitPlot($unitPlot, $site, $platformUriMap);
                $webappBlock->addUnitPlots($res);
            }

            $platformUriMap[$res->getUri()] = $res;
            ++$puCNT;
        }
    }

    public function buildUnitPlot(UnitParcel $unitPlot, Site $site, array &$platformUriMap): UnitPlot
    {
        $res = (new UnitPlot())
            ->setNumber($unitPlot->getName())
            ->setTreatment($platformUriMap[$unitPlot->getTreatment()->getUri()]);

        foreach ($unitPlot->getIndividuals() as $individual) {
            $res->addIndividual($this->buildIndividual($individual, $platformUriMap));
        }

        foreach ($unitPlot->getOutExperimentationZones() as $oez) {
            $res->addOutExperimentationZone($this->buildOutExperimentationZone($oez, $site, $platformUriMap));
        }

        return $res;
    }

    public function buildSurfacicUnitPlot(UnitParcel $unitPlot, array &$platformUriMap): SurfacicUnitPlot
    {
        return (new SurfacicUnitPlot())
            ->setNumber($unitPlot->getName())
            ->setX($unitPlot->getX())
            ->setY($unitPlot->getY())
            ->setIdentifier($unitPlot->getIdent())
            ->setTreatment($platformUriMap[$unitPlot->getTreatment()->getUri()]);
    }

    public function buildIndividual(Individual $individual, array &$platformUriMap): WebappIndividual
    {
        return (new WebappIndividual())
            ->setX($individual->getX())
            ->setY($individual->getY())
            ->setNumber($individual->getName())
            ->setIdentifier($individual->getIdent());
    }

    public function buildProjectFromMobileToWebapp(DataEntryProject $project, WebappPlatform $platform, User $user): WebappProject
    {
        $res = (new WebappProject())
            ->setPlatform($platform)
            ->setName($project->getName());
        $res->setOwner($user);

        foreach ($platform->getExperiments() as $experiment) {
            if ($project->getPlatform()->getDevices()->exists(fn (int $key, Device $device) => $device->getName() === $experiment->getName())) {
                $res->addExperiment($experiment);
            }
        }

        foreach ($project->getStateCodes() as $stateCode) {
            $res->addStateCode($this->buildStateCode($stateCode));
        }
        $variableMap = [];
        foreach (array_merge(
            $project->getUniqueVariables()->toArray(),
            $project->getGeneratorVariables()->toArray(),
        ) as $variable) {
            $variable = $this->constructMissingVariable($variable, $variableMap);
            if ($variable instanceof SimpleVariable) {
                $res->addSimpleVariable($variable);
            } elseif ($variable instanceof WebappGeneratorVariable) {
                $res->addGeneratorVariable($variable);
            }
        }

        return $res;
    }

    public function buildStateCode(StateCode $stateCode): WebappStateCode
    {
        $res = (new WebappStateCode())
            ->setCode($stateCode->getCode())
            ->setTitle($stateCode->getLabel())
            ->setMeaning($stateCode->getDescription());
        switch ($res->getTitle()) {
            case 'Mort':
            case 'Donnée Manquante':
                $res->setPermanent(true);
                break;
            default:
                $res->setPermanent(false);
        }

        switch ($stateCode->getPropagation()) {
            case 'individu':
                $res->setSpreading(SpreadingEnum::INDIVIDUAL);
                break;
            case 'parcelle':
                $res->setSpreading(SpreadingEnum::UNIT_PLOT);
                break;
            default:
                $res->setSpreading(null);
                break;
        }

        return $res;
    }

    private function transformColor(string $color): int
    {
        preg_match('/.*{([0-9]+), *([0-9]+), *([0-9]+)}/', $color, $matches);

        return (int) $matches[1] * 256 * 256 + (int) $matches[2] * 256 + (int) $matches[3];
    }
}
