<?php

/*
 * @author TRYDEA - 2024
 */

namespace Webapp\FileManagement\Service\XmlUtils\Normalizer\Mobile;

use Doctrine\Common\Collections\Collection;
use Mobile\Measure\Entity\Variable\Base\Variable;
use Mobile\Measure\Entity\Variable\Material;
use Mobile\Measure\Entity\Variable\PreviousValue;
use Mobile\Measure\Entity\Variable\Scale;
use Webapp\Core\Entity\Device;
use Webapp\Core\Enumeration\PathLevelEnum;
use Webapp\Core\Enumeration\VariableTypeEnum;
use Webapp\FileManagement\Entity\RequestFile;
use Webapp\FileManagement\Exception\ParsingException;
use Webapp\FileManagement\Service\XmlUtils\Normalizer\AbstractXmlNormalizer;
use Webapp\FileManagement\Service\XmlUtils\Normalizer\XmlImportConfig;

/**
 * @template T of Variable
 * @template TImportDto of VariableXmlDto&array<array-key, mixed>
 *
 * @template-extends AbstractXmlNormalizer<T, TImportDto>
 *
 * @psalm-type VariableXmlDto = array{
 *      "@active": ?bool,
 *      "@commentaire": ?string,
 *      "@dateCreation": ?\DateTime,
 *      "@dateDerniereModification": ?\DateTime,
 *      "@format": ?string,
 *      "@horodatage": ?bool,
 *      "@materiel": ?Device,
 *      "@nbRepetitionSaisies": ?int,
 *      "@nom": ?string,
 *      "@nomCourt": ?string,
 *      "@ordre": ?int,
 *      "@saisieObligatoire": ?bool,
 *      "@typeVariable": ?string,
 *      "@unite": ?string,
 *      "@uniteParcoursType": ?string,
 *      "@valeurDefaut": ?string,
 *      echelle: ?Scale,
 *      mesuresVariablesPrechargees: Collection<int, PreviousValue>
 * }
 */
abstract class VariableNormalizer extends AbstractXmlNormalizer
{
    protected const TYPE_REEL = 'reel';
    protected const TYPE_BOOLEEN = 'booleen';
    protected const TYPE_ENTIERE = 'entiere';
    protected const TYPE_DATE = 'date';
    protected const TYPE_HEURE = 'heure';
    protected const TYPE_ALPHANUMERIQUE = 'alphanumerique';

    protected const TYPE_MAP = [
        VariableTypeEnum::ALPHANUMERIC => self::TYPE_ALPHANUMERIQUE,
        VariableTypeEnum::BOOLEAN => self::TYPE_BOOLEEN,
        VariableTypeEnum::DATE => self::TYPE_DATE,
        VariableTypeEnum::HOUR => self::TYPE_HEURE,
        VariableTypeEnum::INTEGER => self::TYPE_ENTIERE,
        VariableTypeEnum::REAL => self::TYPE_REEL,
    ];
    protected const REVERSE_TYPE_MAP = [
        self::TYPE_REEL => VariableTypeEnum::REAL,
        self::TYPE_BOOLEEN => VariableTypeEnum::BOOLEAN,
        self::TYPE_ENTIERE => VariableTypeEnum::INTEGER,
        self::TYPE_DATE => VariableTypeEnum::DATE,
        self::TYPE_HEURE => VariableTypeEnum::HOUR,
    ];

    protected const UNITE_DISPOSITIF = 'Dispositif';
    protected const UNITE_BLOC = 'Bloc';
    protected const UNITE_SOUS_BLOC = 'Sous Bloc';
    protected const UNITE_INDIVIDU = 'Individu';

    protected const REVERSE_UNITE_PARCOURS_MAP = [
        self::UNITE_DISPOSITIF => PathLevelEnum::EXPERIMENT,
        self::UNITE_BLOC => PathLevelEnum::BLOCK,
        self::UNITE_SOUS_BLOC => PathLevelEnum::SUB_BLOCK,
        self::UNITE_INDIVIDU => PathLevelEnum::INDIVIDUAL,
    ];
    protected const ATTR_DATE_CREATION = '@dateCreation';
    protected const ATTR_DATE_DERNIERE_MODIFICATION = '@dateDerniereModification';
    protected const ATTR_SAISIE_OBLIGATOIRE = '@saisieObligatoire';
    protected const ATTR_NOM = '@nom';
    protected const ATTR_NOM_COURT = '@nomCourt';
    protected const ATTR_TYPE_VARIABLE = '@typeVariable';
    protected const ATTR_NB_REPETITION_SAISIES = '@nbRepetitionSaisies';
    protected const ATTR_ACTIVE = '@active';
    protected const ATTR_ORDRE = '@ordre';
    protected const ATTR_VALEUR_DEFAUT = '@valeurDefaut';
    protected const ATTR_UNITE = '@unite';
    protected const TAG_MESURES_VARIABLES_PRECHARGEES = 'mesuresVariablesPrechargees';
    protected const TAG_ECHELLE = 'echelle';
    protected const ATTR_UNITE_PARCOURS_TYPE = '@uniteParcoursType';
    protected const ATTR_HORODATAGE = '@horodatage';
    protected const ATTR_COMMENTAIRE = '@commentaire';
    protected const ATTR_FORMAT = '@format';
    protected const ATTR_DEBUT = '@debut';
    protected const ATTR_FIN = '@fin';
    protected const ATTR_PRECHARGEE = '@prechargee';
    protected const ATTR_NOM_GENERE = '@nomGenere';
    protected const TAG_VARIABLES_GENEREES = 'variablesGenerees';
    protected const ATTR_MATERIEL = '@materiel';
    protected const XSI_TYPE_VARIABLE_SEMI_AUTO = 'adonis.modeleMetier.projetDeSaisie.variables:VariableSemiAuto';

    /**
     * @param T $object
     */
    protected function extractCommonData($object, array $context): array
    {
        return [
            self::ATTR_NOM => $object->getName(),
            self::ATTR_NOM_COURT => $object->getShortName(),
            self::ATTR_NB_REPETITION_SAISIES => $object->getRepetitions(),
            self::ATTR_UNITE => $object->getUnit(),
            self::ATTR_HORODATAGE => $object->isAskTimestamp(),
            self::ATTR_UNITE_PARCOURS_TYPE => $object->getPathLevel(),
            self::ATTR_DATE_CREATION => $object->getCreationDate(),
            self::ATTR_DATE_DERNIERE_MODIFICATION => $object->getModificationDate(),
            self::ATTR_COMMENTAIRE => $object->getComment(),
            self::ATTR_ORDRE => $object->getOrder(),
            self::ATTR_ACTIVE => $object->isActive(),
            self::ATTR_FORMAT => $object->getFormat(),
            self::ATTR_TYPE_VARIABLE => '' !== $object->getType() ? self::TYPE_MAP[$object->getType()] : null,
            self::ATTR_SAISIE_OBLIGATOIRE => $object->isMandatory(),
            self::ATTR_DEBUT => null !== $object->getMaterial() ? $object->getFrameStartPosition() : null,
            self::ATTR_FIN => null !== $object->getMaterial() ? $object->getFrameEndPosition() : null,
            self::ATTR_XSI_TYPE => null !== $object->getMaterial() ? self::XSI_TYPE_VARIABLE_SEMI_AUTO : null,
            self::ATTR_VALEUR_DEFAUT => '' !== $object->getDefaultValue() ? $object->getDefaultValue() : null,
            self::ATTR_PRECHARGEE => null !== $object->getConnectedDataEntryProject() ? true : null,
        ];
    }

    /**
     * @return array{
     *        "@active": string|XmlImportConfig,
     *        "@commentaire": string|XmlImportConfig,
     *        "@dateCreation": string|XmlImportConfig,
     *        "@dateDerniereModification": string|XmlImportConfig,
     *        "@format": string|XmlImportConfig,
     *        "@horodatage": string|XmlImportConfig,
     *        "@materiel": string|XmlImportConfig,
     *        "@nbRepetitionSaisies": string|XmlImportConfig,
     *        "@nom": string|XmlImportConfig,
     *        "@nomCourt": string|XmlImportConfig,
     *        "@ordre": string|XmlImportConfig,
     *        "@saisieObligatoire": string|XmlImportConfig,
     *        "@typeVariable": string|XmlImportConfig,
     *        "@unite": string|XmlImportConfig,
     *        "@uniteParcoursType": string|XmlImportConfig,
     *        "@valeurDefaut": string|XmlImportConfig,
     *        echelle: string|XmlImportConfig,
     *        mesuresVariablesPrechargees: string|XmlImportConfig,
     * }
     */
    protected function getCommonImportDataConfig(array $context): array
    {
        return [
            self::ATTR_DATE_CREATION => \DateTime::class,
            self::ATTR_DATE_DERNIERE_MODIFICATION => \DateTime::class,
            self::ATTR_SAISIE_OBLIGATOIRE => 'bool',
            self::ATTR_NOM => 'string',
            self::ATTR_NOM_COURT => 'string',
            self::ATTR_TYPE_VARIABLE => 'string',
            self::ATTR_NB_REPETITION_SAISIES => 'int',
            self::ATTR_ACTIVE => 'bool',
            self::ATTR_ORDRE => 'int',
            self::ATTR_FORMAT => 'string',
            self::ATTR_VALEUR_DEFAUT => 'string',
            self::ATTR_UNITE => 'string',
            self::TAG_MESURES_VARIABLES_PRECHARGEES => XmlImportConfig::collection(PreviousValue::class),
            self::TAG_ECHELLE => XmlImportConfig::value(Scale::class),
            self::ATTR_UNITE_PARCOURS_TYPE => 'string',
            self::ATTR_HORODATAGE => 'bool',
            self::ATTR_COMMENTAIRE => 'string',
            self::ATTR_MATERIEL => XmlImportConfig::reference(Material::class),
        ];
    }

    protected function validateImportData($data, array $context): void
    {
        if (
            !isset($data[self::ATTR_NOM])
            || !isset($data[self::ATTR_NOM_COURT])
            || !isset($data[self::ATTR_NB_REPETITION_SAISIES])
            || !isset($data[self::ATTR_DATE_CREATION])
            || !isset($data[self::ATTR_DATE_DERNIERE_MODIFICATION])
        ) {
            throw new ParsingException('', RequestFile::ERROR_STATE_VARIABLE_INFO_MISSING);
        }
        if (isset($data[self::ATTR_NOM_GENERE]) && !isset($data[self::TAG_VARIABLES_GENEREES])) {
            throw new ParsingException('', RequestFile::ERROR_NO_GENERATED_VARIABLE);
        }

        $readerHelper = self::getReaderHelper($context);
        if (isset($data[self::ATTR_XSI_TYPE]) && self::XSI_TYPE_VARIABLE_SEMI_AUTO === $data[self::ATTR_XSI_TYPE]) {
            if (!isset($data[self::ATTR_MATERIEL]) || !$readerHelper->exists($data[self::ATTR_MATERIEL])) {
                throw new ParsingException('', RequestFile::ERROR_UNKNOWN_MATERIAL);
            }
        }
    }

    /**
     * @param T          $object
     * @param TImportDto $data
     *
     * @return T
     */
    protected function completeImportedObject($object, $data, ?string $format, array $context)
    {
        $object
            ->setCreationDate($data[self::ATTR_DATE_CREATION])
            ->setModificationDate($data[self::ATTR_DATE_DERNIERE_MODIFICATION])
            ->setMandatory($data[self::ATTR_SAISIE_OBLIGATOIRE] ?? false)
            ->setName($data[self::ATTR_NOM])
            ->setShortName($data[self::ATTR_NOM_COURT])
            ->setRepetitions($data[self::ATTR_NB_REPETITION_SAISIES])
            ->setType(self::REVERSE_TYPE_MAP[$data[self::ATTR_TYPE_VARIABLE] ?? ''] ?? VariableTypeEnum::ALPHANUMERIC)
            ->setActive($data[self::ATTR_ACTIVE] ?? false)
            ->setOrder($data[self::ATTR_ORDRE] ?? 0)
            ->setPathLevel(self::REVERSE_UNITE_PARCOURS_MAP[$data[self::ATTR_UNITE_PARCOURS_TYPE] ?? ''] ?? PathLevelEnum::UNIT_PLOT)
            ->setAskTimestamp($data[self::ATTR_HORODATAGE] ?? false)
            ->setComment($data[self::ATTR_COMMENTAIRE])
            ->setDefaultValue($data[self::ATTR_VALEUR_DEFAUT])
            ->setUnit($data[self::ATTR_UNITE])
            ->setScale($data[self::TAG_ECHELLE])
            ->setPreviousValues($data[self::TAG_MESURES_VARIABLES_PRECHARGEES]);

        if (VariableTypeEnum::REAL === $object->getType()) {
            $object->setFormat('1');
        }
        if (null !== $data[self::ATTR_FORMAT] && '' !== $data[self::ATTR_FORMAT]) {
            $object->setFormat($data[self::ATTR_FORMAT]);
        }

        if (isset($data[self::ATTR_XSI_TYPE]) && self::XSI_TYPE_VARIABLE_SEMI_AUTO === $data[self::ATTR_XSI_TYPE]) {
            $object->setFrameStartPosition((int) $data[self::ATTR_DEBUT]);
            $object->setFrameEndPosition((int) $data[self::ATTR_FIN]);

            $data[self::ATTR_MATERIEL]->addVariable($object);
        }

        return $object;
    }
}
