<?php

namespace App\Model\Table;

use App\Model\Table\AppTable;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Validation\Validator;
use Cake\Utility\Hash;
use Cake\Utility\Inflector;
use Cake\Utility\Text;
use Cake\Filesystem\File;
use Cake\Filesystem\Folder;
use App\Model\Entity\MetaTemplate;

class MetaTemplatesTable extends AppTable
{
    public const TEMPLATE_PATH = [
        ROOT . '/libraries/default/meta_fields/',
        ROOT . '/libraries/custom/meta_fields/'
    ];

    public const UPDATE_STRATEGY_CREATE_NEW = 'create_new';
    public const UPDATE_STRATEGY_UPDATE_EXISTING = 'update_existing';
    public const UPDATE_STRATEGY_KEEP_BOTH = 'keep_both';
    public const UPDATE_STRATEGY_DELETE_ALL = 'delete_all';

    public const DEFAULT_STRATEGY = MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING;
    public const ALLOWED_STRATEGIES = [
        MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW,
        MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING,
        MetaTemplatesTable::UPDATE_STRATEGY_KEEP_BOTH,
        MetaTemplatesTable::UPDATE_STRATEGY_DELETE_ALL,
    ];

    private $templatesOnDisk = null;

    public function initialize(array $config): void
    {
        parent::initialize($config);
        $this->addBehavior('Timestamp');
        $this->hasMany(
            'MetaTemplateFields',
            [
                'foreignKey' => 'meta_template_id',
                'saveStrategy' => 'replace',
                'dependent' => true,
                'cascadeCallbacks' => true,
            ]
        );
        $this->hasOne('MetaTemplateNameDirectory')
            ->setForeignKey('meta_template_directory_id');

        $this->setDisplayField('name');
    }

    public function validationDefault(Validator $validator): Validator
    {
        $validator
            ->notEmptyString('scope')
            ->notEmptyString('name')
            ->notEmptyString('namespace')
            ->notEmptyString('uuid')
            ->notEmptyString('version')
            ->notEmptyString('source')
            ->requirePresence(['scope', 'source', 'version', 'uuid', 'name', 'namespace', 'meta_template_directory_id'], 'create');
        return $validator;
    }

    public function isStrategyAllowed(string $strategy): bool
    {
        return in_array($strategy, MetaTemplatesTable::ALLOWED_STRATEGIES);
    }

    /**
     * Load the templates stored on the disk update and create them in the database without touching at the existing ones
     *
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @return array The update result containing potential errors and the successes
     */
    public function updateAllTemplates(): array
    {
        // Create new template found on the disk
        $newTemplateResult = $this->createAllNewTemplates();
        $files_processed = $newTemplateResult['files_processed'];
        $updatesErrors = $newTemplateResult['update_errors'];

        $templatesUpdateStatus = $this->getUpdateStatusForTemplates();

        // Update all existing templates
        foreach ($templatesUpdateStatus as $uuid => $templateUpdateStatus) {
            if (!empty($templateUpdateStatus['existing_template'])) {
                $metaTemplate = $templateUpdateStatus['existing_template'];
                $result = $this->update($metaTemplate, null);
                if ($result['success']) {
                    $files_processed[] = $metaTemplate->uuid;
                }
                if (!empty($result['errors'])) {
                    $updatesErrors[] = $result['errors'];
                }
            }
        }
        $results = [
            'update_errors' => $updatesErrors,
            'files_processed' => $files_processed,
            'success' => !empty($files_processed),
        ];
        return $results;
    }

    /**
     * Load the templates stored on the disk update and create them in the database without touching at the existing ones
     *
     * @return array The update result containing potential errors and the successes
     */
    public function createAllNewTemplates(): array
    {
        $updatesErrors = [];
        $files_processed = [];
        $templatesOnDisk = $this->readTemplatesFromDisk();
        $templatesUpdateStatus = $this->getUpdateStatusForTemplates();
        foreach ($templatesOnDisk as $template) {
            $errors = [];
            $success = false;
            $updateStatus = $templatesUpdateStatus[$template['uuid']];
            if ($this->isStrategyAllowed(MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW) && $updateStatus['new']) {
                $success = $this->saveNewMetaTemplate($template, $errors);
            }
            if ($success) {
                $files_processed[] = $template['uuid'];
            } else {
                $updatesErrors[] = $errors;
            }
        }
        $results = [
            'update_errors' => $updatesErrors,
            'files_processed' => $files_processed,
            'success' => !empty($files_processed),
        ];
        return $results;
    }

    /**
     * Load the template stored on the disk for the provided meta-template and update it using the optional strategy.
     *
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @param string|null $strategy The strategy to be used when updating templates with conflicts
     * @return array The update result containing potential errors and the successes
     */
    public function update(\App\Model\Entity\MetaTemplate $metaTemplate, $strategy = null): array
    {
        $files_processed = [];
        $updatesErrors = [];
        $templateOnDisk = $this->readTemplateFromDisk($metaTemplate->uuid);
        $templateStatus = $this->getStatusForMetaTemplate($templateOnDisk, $metaTemplate);
        $updateStatus = $this->computeFullUpdateStatusForMetaTemplate($templateStatus, $metaTemplate);
        $errors = [];

        $result = $this->doUpdate($updateStatus, $templateOnDisk, $metaTemplate, $strategy);
        if ($result['success']) {
            $files_processed[] = $templateOnDisk['uuid'];
        }
        if (!empty($result['errors'])) {
            $updatesErrors[] = implode(', ', Hash::extract($result['errors'], '{n}.message'));
        }
        $results = [
            'update_errors' => $updatesErrors,
            'files_processed' => $files_processed,
            'success' => !empty($files_processed),
        ];
        return $results;
    }

    private function doUpdate(array $updateStatus, array $templateOnDisk, \App\Model\Entity\MetaTemplate $metaTemplate, string $strategy = null): array
    {
        $errors = [];
        $success = false;
        if ($updateStatus['up-to-date']) {
            $errors['message'] = __('Meta-template already up-to-date');
            $success = true;
        } else if ($this->isStrategyAllowed(MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW) && $updateStatus['is-new']) {
            $success = $this->saveNewMetaTemplate($templateOnDisk, $errors);
        } else if ($this->isStrategyAllowed(MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING) && $updateStatus['automatically-updateable']) {
            $success = $this->updateMetaTemplate($metaTemplate, $templateOnDisk, $errors);
        } else if (!$updateStatus['up-to-date'] && (!is_null($strategy) && !$this->isStrategyAllowed($strategy))) {
            $errors['message'] = __('Cannot update meta-template, update strategy not allowed');
        } else if (!$updateStatus['up-to-date']) {
            $strategy = is_null($strategy) ? MetaTemplatesTable::DEFAULT_STRATEGY : $strategy;
            if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING && !$updateStatus['automatically-updateable']) {
                $strategy = MetaTemplatesTable::UPDATE_STRATEGY_KEEP_BOTH;
            }
            $success = $this->updateMetaTemplateWithStrategyRouter($metaTemplate, $templateOnDisk, $strategy, $errors);
        } else {
            $errors['message'] = __('Could not update. Something went wrong.');
        }
        return [
            'success' => $success,
            'errors' => $errors,
        ];
    }

    /**
     * Load the templates stored on the disk update and create the one having the provided UUID in the database
     * Will do nothing if the UUID is already known
     *
     * @param string $uuid
     * @return array The update result containing potential errors and the successes
     */
    public function createNewTemplate(string $uuid): array
    {
        $templateOnDisk = $this->readTemplateFromDisk($uuid);
        $templateStatus = $this->getUpdateStatusForTemplate($templateOnDisk);
        $errors = [];
        $updatesErrors = [];
        $files_processed = [];
        $savedMetaTemplate = null;
        $success = false;
        if (empty($templateStatus['new'])) {
            $error['message'] = __('Template UUID already exists');
            $success = true;
        } else if ($this->isStrategyAllowed(MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW)) {
            $success = $this->saveNewMetaTemplate($templateOnDisk, $errors, $savedMetaTemplate);
        } else {
            $errors['message'] = __('Could not create template. Something went wrong.');
        }
        if ($success) {
            $files_processed[] = $templateOnDisk['uuid'];
        }
        if (!empty($errors)) {
            $updatesErrors[] = $errors;
        }
        $results = [
            'update_errors' => $updatesErrors,
            'files_processed' => $files_processed,
            'success' => !empty($files_processed),
        ];
        return $results;
    }

    /**
     * Load the templates stored on the disk and compute their update status.
     * Only compute the result if an UUID is provided
     *
     * @param string|null $template_uuid
     * @return array
     */
    public function getUpdateStatusForTemplates(): array
    {
        $errors = [];
        $templateUpdatesStatus = [];
        $templates = $this->readTemplatesFromDisk($errors);
        foreach ($templates as $template) {
            $templateUpdatesStatus[$template['uuid']] = $this->getUpdateStatusForTemplate($template);
        }
        return $templateUpdatesStatus;
    }


    /**
     * Checks if the template is update-to-date from the provided update status
     *
     * @param array $updateStatus
     * @return boolean
     */
    public function isUpToDate(array $updateStatus): bool
    {
        return !empty($updateStatus['up-to-date']) || !empty($updateStatus['new']);
    }

    /**
     * Checks if the template is updateable automatically from the provided update status
     *
     * @param array $updateStatus
     * @return boolean
     */
    public function isAutomaticallyUpdateable(array $updateStatus): bool
    {
        return !empty($updateStatus['automatically-updateable']);
    }

    /**
     * Checks if the template is new (and not loaded in the database yet) from the provided update status
     *
     * @param array $updateStatus
     * @return boolean
     */
    public function isNew(array $updateStatus): bool
    {
        return $updateStatus['new'];
    }

    /**
     * Checks if the template has no conflicts that would prevent an automatic update from the provided update status
     *
     * @param array $updateStatus
     * @return boolean
     */
    public function hasNoConflict(array $updateStatus): bool
    {
        return $this->hasConflict($updateStatus);
    }

    /**
     * Checks if the template has conflict preventing an automatic update from the provided update status
     *
     * @param array $updateStatus
     * @return boolean
     */
    public function hasConflict(array $updateStatus): bool
    {
        return empty($updateStatus['automatically-updateable']) && empty($updateStatus['up-to-date']) && empty($updateStatus['new']);
    }

    /**
     * Checks if the metaTemplate can be updated to a newer version loaded in the database
     *
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @return boolean
     */
    public function isUpdateableToExistingMetaTemplate(\App\Model\Entity\MetaTemplate $metaTemplate): bool
    {
        $newestTemplate = $this->getNewestVersion($metaTemplate);
        return !empty($newestTemplate);
    }

    /**
     * Checks if the template can be removed from the database for the provided update status.
     * A template can be removed if a newer version is already loaded in the database and no meta-fields are using it.
     *
     * @param array $updateStatus
     * @return boolean
     */
    public function isRemovable(array $updateStatus): bool
    {
        return !empty($updateStatus['can-be-removed']);
    }

    /**
     * Compute the state from the provided update status and metaTemplate
     *
     * @param array $updateStatus
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @return array
     */
    public function computeFullUpdateStatusForMetaTemplate(array $updateStatus, \App\Model\Entity\MetaTemplate $metaTemplate): array
    {
        return [
            'up-to-date' => $this->isUpToDate($updateStatus),
            'automatically-updateable' => $this->isAutomaticallyUpdateable($updateStatus),
            'is-new' => $this->isNew($updateStatus),
            'has-conflict' => $this->hasConflict($updateStatus),
            'to-existing' => $this->isUpdateableToExistingMetaTemplate($metaTemplate),
            'can-be-removed' => $this->isRemovable($updateStatus),
        ];
    }

    /**
     * Get the update status of meta-templates that are up-to-date in regards to the template stored on the disk.
     *
     * @param array|null $updateStatus
     * @return array The list of update status for up-to-date templates
     */
    public function getUpToDateTemplates($updatesStatus = null): array
    {
        $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus;
        foreach ($updatesStatus as $uuid => $updateStatus) {
            if (!$this->isUpToDate($updateStatus)) {
                unset($updatesStatus[$uuid]);
            }
        }
        return $updatesStatus;
    }

    /**
     * Get the update status of meta-templates that are not up-to-date in regards to the template stored on the disk.
     *
     * @param array|null $updateResult
     * @return array The list of update status for non up-to-date templates
     */
    public function getNotUpToDateTemplates($updatesStatus = null): array
    {
        $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus;
        foreach ($updatesStatus as $uuid => $updateStatus) {
            if ($this->isUpToDate($updateStatus)) {
                unset($updatesStatus[$uuid]);
            }
        }
        return $updatesStatus;
    }

    /**
     * Get the update status of meta-templates that are automatically updateable in regards to the template stored on the disk.
     *
     * @param array|null $updateResult
     * @return array The list of update status for non up-to-date templates
     */
    public function getAutomaticallyUpdateableTemplates($updatesStatus = null): array
    {
        $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus;
        foreach ($updatesStatus as $uuid => $updateStatus) {
            if (!$this->isAutomaticallyUpdateable($updateStatus)) {
                unset($updatesStatus[$uuid]);
            }
        }
        return $updatesStatus;
    }

    /**
     * Get the update status of meta-templates that are new in regards to the template stored on the disk.
     *
     * @param array|null $updateResult
     * @return array The list of update status for new templates
     */
    public function getNewTemplates($updatesStatus = null): array
    {
        $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus;
        foreach ($updatesStatus as $uuid => $updateStatus) {
            if (!$this->isNew($updateStatus)) {
                unset($updatesStatus[$uuid]);
            }
        }
        return $updatesStatus;
    }

    /**
     * Get the update status of meta-templates that have conflict preventing an automatic update in regards to the template stored on the disk.
     *
     * @param array|null $updateResult
     * @return array The list of update status for new templates
     */
    public function getConflictTemplates($updatesStatus = null): array
    {
        $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus;
        foreach ($updatesStatus as $uuid => $updateStatus) {
            if (!$this->hasConflict($updateStatus)) {
                unset($updatesStatus[$uuid]);
            }
        }
        return $updatesStatus;
    }

    /**
     * Get the latest (having the higher version) meta-template loaded in the database for the provided meta-template
     *
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @param boolean $full
     * @return \App\Model\Entity\MetaTemplate|null
     */
    public function getNewestVersion(\App\Model\Entity\MetaTemplate $metaTemplate, bool $full = false)
    {
        $query = $this->find()->where([
            'uuid' => $metaTemplate->uuid,
            'id !=' => $metaTemplate->id,
            'version >=' => $metaTemplate->version,
        ])
            ->order(['version' => 'DESC']);
        if ($full) {
            $query->contain(['MetaTemplateFields']);
        }
        $newestTemplate = $query->first();
        return $newestTemplate;
    }

    /**
     * Generate and return a query (to be used as a subquery) resolving to the IDs of the latest version of a saved meta-template
     *
     * @return  \Cake\ORM\Query
     */
    public function genQueryForAllNewestVersionIDs(): \Cake\ORM\Query
    {
        /**
         * SELECT a.id FROM meta_templates a INNER JOIN (
         *   SELECT uuid, MAX(version) maxVersion FROM meta_templates GROUP BY uuid
         * ) b on a.uuid = b.uuid AND a.version = b.maxVersion;
         */
         $query = $this->find()
            ->select([
                'id'
            ])
            ->join([
                't' => [
                    'table'      => '(SELECT uuid, MAX(version) AS maxVersion FROM meta_templates GROUP BY uuid)',
                    'type'       => 'INNER',
                    'conditions' => [
                        't.uuid = MetaTemplates.uuid',
                        't.maxVersion = MetaTemplates.version'
                    ],
                ],
            ]);
        return $query;
    }

    /**
     * Get the update status of meta-templates that can be removed.
     *
     * @param array|null $updateResult
     * @return array The list of update status for new templates
     */
    public function getCanBeRemovedTemplates($updatesStatus = null): array
    {
        $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus;
        foreach ($updatesStatus as $i => $updateStatus) {
            if (!$this->isRemovable($updateStatus)) {
                unset($updatesStatus[$i]);
            }
        }
        return $updatesStatus;
    }

    /**
     * Reads all template stored on the disk and parse them
     *
     * @param array|null $errors Contains errors while parsing the meta-templates
     * @return array The parsed meta-templates stored on the disk
     */
    public function readTemplatesFromDisk(&$errors = []): array
    {
        if (!is_null($this->templatesOnDisk)) {
            return $this->templatesOnDisk;
        }
        $templates = [];
        $errors = [];
        foreach (self::TEMPLATE_PATH as $path) {
            if (is_dir($path)) {
                $files = scandir($path);
                foreach ($files as $k => $file) {
                    if (substr($file, -5) === '.json') {
                        $errorMessage = '';
                        $template = $this->decodeTemplateFromDisk($path . $file, $errorMessage);
                        if (!empty($template)) {
                            $templates[] = $template;
                        } else {
                            $errors[] = $errorMessage;
                        }
                    }
                }
            }
        }
        $this->templatesOnDisk = $templates;
        return $templates;
    }

    /**
     * Read and parse the meta-template stored on disk having the provided UUID
     *
     * @param string $uuid
     * @param string $error Contains the error while parsing the meta-template
     * @return array|null The meta-template or null if not templates matche the provided UUID
     */
    public function readTemplateFromDisk(string $uuid, &$error = ''): ?array
    {
        foreach (self::TEMPLATE_PATH as $path) {
            if (is_dir($path)) {
                $files = scandir($path);
                foreach ($files as $k => $file) {
                    if (substr($file, -5) === '.json') {
                        $errorMessage = '';
                        $template = $this->decodeTemplateFromDisk($path . $file, $errorMessage);
                        if (!empty($errorMessage)) {
                            $error = $errorMessage;
                            return null;
                        }
                        if (!empty($template) && $template['uuid'] == $uuid) {
                            return $template;
                        }
                    }
                }
            }
        }
        $error = __('Could not find meta-template with UUID {0}', $uuid);
        return null;
    }

    /**
     * Read and decode the meta-template located at the provided path
     *
     * @param string $filePath
     * @param string $errorMessage
     * @return array|null The meta-template or null if there was an error while trying to decode
     */
    public function decodeTemplateFromDisk(string $filePath, &$errorMessage = ''): ?array
    {
        $file = new File($filePath, false);
        if ($file->exists()) {
            $filename = $file->name();
            $content = $file->read();
            if (empty($content)) {
                $errorMessage = __('Could not read template file `{0}`.', $filename);
                return null;
            }
            $metaTemplate = json_decode($content, true);
            if (empty($metaTemplate)) {
                $errorMessage = __('Could not load template file `{0}`. Error while decoding the template\'s JSON', $filename);
                return null;
            }
            if (empty($metaTemplate['uuid']) || empty($metaTemplate['version'])) {
                $errorMessage = __('Could not load template file. Invalid template file. Missing template UUID or version');
                return null;
            }
            return $metaTemplate;
        }
        $errorMessage = __('File does not exists');
        return null;
    }

    /**
     * Collect all enties having meta-fields belonging to the provided template
     *
     * @param integer $template_id
     * @param integer|bool $limit The limit of entities to be returned. Pass null to be ignore the limit
     * @return array List of entities
     */
    public function getEntitiesHavingMetaFieldsFromTemplate(int $metaTemplateId, $limit=10, int &$totalAmount=0): array
    {
        $metaTemplate = $this->get($metaTemplateId);
        $queryParentEntities = $this->MetaTemplateFields->MetaFields->find();
        $queryParentEntities
            ->select(['parent_id'])
            ->where([
                'meta_template_id' => $metaTemplateId
            ])
            ->group(['parent_id']);

        $entitiesTable = $this->getTableForMetaTemplateScope($metaTemplate);
        $entityQuery = $entitiesTable->find()
            ->where(['id IN' => $queryParentEntities])
            ->contain([
                'MetaFields' => [
                    'conditions' => [
                        'meta_template_id' => $metaTemplateId
                    ]
                ]
            ]);
        if (!is_null($limit)) {
            $totalAmount = $entityQuery->all()->count();
            $entityQuery->limit($limit);
        }
        $entities = $entityQuery->all()->toList();
        return $entities;
    }

    /**
     * Get the table linked to the meta-template
     *
     * @param \App\Model\Entity\MetaTemplate|string $metaTemplate
     * @return \App\Model\Table\AppTable
     */
    private function getTableForMetaTemplateScope($metaTemplateOrScope): \App\Model\Table\AppTable
    {
        if (is_string($metaTemplateOrScope)) {
            $scope = $metaTemplateOrScope;
        } else {
            $scope = $metaTemplateOrScope->scope;
        }
        $entitiesClassName = Inflector::camelize(Inflector::pluralize($scope));
        $entitiesTable = TableRegistry::getTableLocator()->get($entitiesClassName);
        return $entitiesTable;
    }

    /**
     * Get the meta-field keyed by their template_id and meta_template_id belonging to the provided entity
     *
     * @param integer $entity_id The entity for which the meta-fields belongs to
     * @param array $conditions Additional conditions to be passed to the meta-fields query
     * @return array The associated array containing the meta-fields keyed by their meta-template and meta-template-field IDs
     */
    public function getKeyedMetaFieldsForEntity(int $entity_id, array $conditions = []): array
    {
        $query = $this->MetaTemplateFields->MetaFields->find();
        $query->where(array_merge(
            $conditions,
            [
                'MetaFields.parent_id' => $entity_id
            ]
        ));
        $metaFields = $query->all();
        $keyedMetaFields = [];
        foreach ($metaFields as $metaField) {
            if (empty($keyedMetaFields[$metaField->meta_template_id][$metaField->meta_template_field_id])) {
                $keyedMetaFields[$metaField->meta_template_id][$metaField->meta_template_field_id] = [];
            }
            $keyedMetaFields[$metaField->meta_template_id][$metaField->meta_template_field_id][$metaField->id] = $metaField;
        }
        return $keyedMetaFields;
    }

    /**
     * Insert the keyed meta-fields into the provided meta-templates
     *
     * @param array $keyedMetaFields An associative array containing the meta-fields keyed by their meta-template and meta-template-field IDs
     * @param array $metaTemplates List of meta-templates
     * @return array The list of meta-template with the meta-fields inserted
     */
    public function insertMetaFieldsInMetaTemplates(array $keyedMetaFields, array $metaTemplates): array
    {
        $merged = [];
        foreach ($metaTemplates as $metaTemplate) {
            $metaTemplate['meta_template_fields'] = Hash::combine($metaTemplate['meta_template_fields'], '{n}.id', '{n}');
            $merged[$metaTemplate->id] = $metaTemplate;
            if (isset($keyedMetaFields[$metaTemplate->id])) {
                foreach ($metaTemplate->meta_template_fields as $j => $meta_template_field) {
                    if (isset($keyedMetaFields[$metaTemplate->id][$meta_template_field->id])) {
                        $merged[$metaTemplate->id]->meta_template_fields[$j]['metaFields'] = $keyedMetaFields[$metaTemplate->id][$meta_template_field->id];
                    } else {
                        $merged[$metaTemplate->id]->meta_template_fields[$j]['metaFields'] = [];
                    }
                }
            }
        }
        return $merged;
    }

    /**
     * Retreive the entity associated for the provided meta-template and id
     *
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @param integer $entity_id
     * @return \App\Model\Entity\AppModel
     */
    public function getEntity(\App\Model\Entity\MetaTemplate $metaTemplate, int $entity_id): \App\Model\Entity\AppModel
    {
        $entitiesTable = $this->getTableForMetaTemplateScope($metaTemplate);
        $entity = $entitiesTable->get($entity_id, [
            'contain' => 'MetaFields'
        ]);
        return $entity;
    }

    /**
     * Collect the unique default template for each scope
     *
     * @param string|null $scope
     * @return array The list of default template
     */
    public function getDefaultTemplatePerScope($scope = null): array
    {
        $query = $this->find('list', [
            'keyField' => 'scope',
            'valueField' => function ($template) {
                return $template;
            }
        ])->where(['is_default' => true]);
        if (!empty($scope)) {
            $query->where(['scope' => $scope]);
        }
        return $query->all()->toArray();
    }

    /**
     * Remove the default flag for all meta-templates belonging to the provided scope
     *
     * @param string $scope
     * @return int the number of updated rows
     */
    public function removeDefaultFlag(string $scope): int
    {
        return $this->updateAll(
            ['is_default' => false],
            ['scope' => $scope]
        );
    }

    /**
     * Check if the provided template can be saved in the database without creating duplicate template in regards to the UUID and version
     *
     * @param array $template
     * @return boolean
     */
    public function canBeSavedWithoutDuplicates(array $template): bool
    {
        $query = $this->find()->where([
            'uuid' => $template['uuid'],
            'version' => $template['version'],
        ]);
        return $query->count() == 0;
    }

    /**
     * Create and save the provided template in the database
     *
     * @param array $template The template to be saved
     * @param array $errors The list of errors that occured during the save process
     * @param \App\Model\Entity\MetaTemplate $savedMetaTemplate The metaTemplate entity that has just been saved
     * @return boolean True if the save was successful, False otherwise
     */
    public function saveNewMetaTemplate(array $template, array &$errors = [], \App\Model\Entity\MetaTemplate &$savedMetaTemplate = null): bool
    {
        if (!$this->canBeSavedWithoutDuplicates($template)) {
            $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']);
        }
        $template['meta_template_fields'] = $template['metaFields'];
        unset($template['metaFields']);
        $metaTemplate = $this->newEntity($template, [
            'associated' => ['MetaTemplateFields']
        ]);
        $metaTemplateDirectory = $this->MetaTemplateNameDirectory->createFromMetaTemplate($metaTemplate);
        $metaTemplate->meta_template_directory_id = $metaTemplateDirectory->id;
        $tmp = $this->save($metaTemplate, [
            'associated' => ['MetaTemplateFields']
        ]);
        if ($tmp === false) {
            $errors[] = new UpdateError(false, __('Could not save the template.'), $metaTemplate->getErrors());
            return false;
        }
        $savedMetaTemplate = $tmp;
        return true;
    }

    /**
     * Update an existing meta-template and save it in the database
     *
     * @param \App\Model\Entity\MetaTemplate $metaTemplate The meta-template to update
     * @param array $template The template to use to update the existing meta-template
     * @param array $errors
     * @return boolean True if the save was successful, False otherwise
     */
    public function updateMetaTemplate(\App\Model\Entity\MetaTemplate $metaTemplate, array $template, array &$errors = []): bool
    {
        if (!$this->canBeSavedWithoutDuplicates($template)) {
            $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']);
        }
        if (is_string($metaTemplate)) {
            $errors[] = new UpdateError(false, $metaTemplate);
            return false;
        }
        $metaTemplate = $this->patchEntity($metaTemplate, $template);
        foreach ($template['metaFields'] as $newMetaField) {
            $newMetaField['__patched'] = true;
            foreach($metaTemplate->meta_template_fields as $i => $oldMetaField) {
                if ($oldMetaField->field == $newMetaField['field']) {
                    $metaTemplate->meta_template_fields[$i] = $this->MetaTemplateFields->patchEntity($oldMetaField, $newMetaField);
                    continue 2;
                }
            }
            $metaTemplate->meta_template_fields[] = $this->MetaTemplateFields->newEntity($newMetaField);
        }
        $metaTemplate->setDirty('meta_template_fields', true);
        $metaTemplate = $this->save($metaTemplate, [
            'associated' => ['MetaTemplateFields']
        ]);
        foreach ($metaTemplate->meta_template_fields as $savedMetafield) {
            if (empty($savedMetafield['__patched'])) {
                $this->MetaTemplateFields->delete($savedMetafield);
            }
        }
        if (empty($metaTemplate)) {
            $errors[] = new UpdateError(false, __('Could not save the template.'), $metaTemplate->getErrors());
            return false;
        }
        return true;
    }

    /**
     * Update an existing meta-template with the provided strategy and save it in the database
     *
     * @param \App\Model\Entity\MetaTemplate $metaTemplate The meta-template to update
     * @param array $template The template to use to update the existing meta-template
     * @param string $strategy The strategy to use when handling update conflicts
     * @param array $errors
     * @return boolean True if the save was successful, False otherwise
     */
    public function updateMetaTemplateWithStrategyRouter(\App\Model\Entity\MetaTemplate $metaTemplate, array $template, string $strategy, array &$errors = []): bool
    {
        if (!$this->canBeSavedWithoutDuplicates($template)) {
            $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']);
        }
        if (is_string($metaTemplate)) {
            $errors[] = new UpdateError(false, $metaTemplate);
            return false;
        }
        if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_KEEP_BOTH) {
            $result = $this->executeStrategyKeep($template, $metaTemplate);
        } else if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING) {
            $result = $this->updateMetaTemplate($metaTemplate, $template);
        } else if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW) {
            $result = $this->executeStrategyCreateNew($template, $metaTemplate);
        } else if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_DELETE_ALL) {
            $result = $this->executeStrategyDeleteAll($template, $metaTemplate);
        } else {
            $errors[] = new UpdateError(false, __('Invalid strategy {0}', $strategy));
            return false;
        }
        if (is_string($result)) {
            $errors[] = new UpdateError(false, $result);
            return false;
        }
        return true;
    }

    /**
     * Execute the `keep_both` update strategy by creating a new meta-template and moving non-conflicting entities to this one.
     * Strategy:
     * - Old template remains untouched
     * - Create new template
     * - Migrate all non-conflicting meta-fields for one entity to the new template
     * - Keep all the conflicting meta-fields for one entity on the old template
     *
     * @param array $template
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @return bool|string If the new template could be saved or the error message
     */
    public function executeStrategyKeep(array $template, \App\Model\Entity\MetaTemplate $metaTemplate)
    {
        if (!$this->canBeSavedWithoutDuplicates($template)) {
            $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']);
        }
        $conflicts = $this->getMetaTemplateConflictsForMetaTemplate($metaTemplate, $template);
        $conflictingEntities = Hash::combine($conflicts, '{s}.conflictingEntities.{n}.parent_id', '{s}.conflictingEntities.{n}.parent_id');
        $blockingConflict = Hash::extract($conflicts, '{s}.conflicts');
        $errors = [];
        if (empty($blockingConflict)) { // No conflict, everything can be updated without special care
            $this->updateMetaTemplate($metaTemplate, $template, $errors);
            return !empty($errors) ? $errors[0] : true;
        }
        $entities = $this->getEntitiesHavingMetaFieldsFromTemplate($metaTemplate->id, null);

        foreach ($entities as $entity) {
            $conflicts = $this->getMetaFieldsConflictsUnderTemplate($entity['meta_fields'], $template);
            if (!empty($conflicts)) {
                $conflictingEntities[$entity->id] = $entity->id;
            }
        }
        if (empty($conflictingEntities)) {
            $this->updateMetaTemplate($metaTemplate, $template, $errors);
            return !empty($errors) ? $errors[0] : true;
        }
        $template['is_default'] = $metaTemplate->is_default;
        $template['enabled'] = $metaTemplate->enabled;
        $metaTemplate->set('enabled', false);
        if ($metaTemplate->is_default) {
            $metaTemplate->set('is_default', false);
            $this->save($metaTemplate);
        }
        $savedMetaTemplate = null;
        $this->saveNewMetaTemplate($template, $errors, $savedMetaTemplate);
        if (!empty($savedMetaTemplate)) {
            foreach ($entities as $entity) {
                if (empty($conflictingEntities[$entity->id])) { // conflicting entities remain untouched
                    $this->supersedeMetaFieldsWithMetaTemplateField($entity['meta_fields'], $savedMetaTemplate);
                }
            }
        } else {
            return $errors[0]->message;
        }
        return true;
    }

    public function migrateMetafieldsToNewestTemplate(\App\Model\Entity\MetaTemplate $oldMetaTemplate, \App\Model\Entity\MetaTemplate $newestMetaTemplate, bool $forceMigration): array
    {
        $result = [
            'success' => true,
            'migrated_count' => 0,
            'conflicting_entities' => 0,
            'migration_errors' => 0,
        ];

        $entities = $this->getEntitiesHavingMetaFieldsFromTemplate($oldMetaTemplate->id, null);
        if (empty($entities)) {
            return $result;
        }

        $successfullyMigratedEntities = 0;
        $migrationErrors = 0;
        $conflictingEntities = [];
        foreach ($entities as $entity) {
            $conflicts = $this->getMetaFieldsConflictsUnderTemplate($entity->meta_fields, $newestMetaTemplate);
            if (!empty($conflicts)) {
                $conflictingEntities[] = $entity->id;
                if (!$forceMigration) {
                    continue;
                } else {
                    $conflictingMetafieldIDs = Hash::extract($conflicts, '{n}.id');
                    $metaFieldsToDelete = [];
                    foreach ($entity->meta_fields as $i => $metaField) {
                        if (in_array($metaField->id, $conflictingMetafieldIDs)) {
                            $metaFieldsToDelete[] = $metaField;
                            unset($entity->meta_fields[$i]);
                        }
                    }
                    $this->MetaTemplateFields->MetaFields->unlink($entity, $metaFieldsToDelete);
                }
            }
            $success = $this->supersedeMetaFieldsWithMetaTemplateField($entity->meta_fields, $newestMetaTemplate);
            if ($success) {
                $successfullyMigratedEntities += 1;
            } else {
                $migrationErrors += 1;
            }
        }

        $result['success'] = $migrationErrors == 0;
        $result['migrated_count'] = $successfullyMigratedEntities;
        $result['conflicting_entities'] = count($conflictingEntities);
        $result['migration_errors'] = $migrationErrors;
        return $result;
    }

    /**
     * Execute the `delete_all` update strategy by updating the meta-template and deleting all conflicting meta-fields.
     * Strategy:
     * - Delete conflicting meta-fields
     * - Update template to the new version
     *
     * @param array $template
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @return bool|string If the new template could be saved or the error message
     */
    public function executeStrategyDeleteAll(array $template, \App\Model\Entity\MetaTemplate $metaTemplate)
    {
        if (!$this->canBeSavedWithoutDuplicates($template)) {
            $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']);
        }
        $errors = [];
        $conflicts = $this->getMetaTemplateConflictsForMetaTemplate($metaTemplate, $template);
        $blockingConflict = Hash::extract($conflicts, '{s}.conflicts');
        if (empty($blockingConflict)) { // No conflict, everything can be updated without special care
            $this->updateMetaTemplate($metaTemplate, $template, $errors);
            return !empty($errors) ? $errors[0] : true;
        }
        $entities = $this->getEntitiesHavingMetaFieldsFromTemplate($metaTemplate->id, null);

        foreach ($entities as $entity) {
            $conflicts = $this->getMetaFieldsConflictsUnderTemplate($entity['meta_fields'], $template);
            $this->MetaTemplateFields->MetaFields->unlink($entity, $conflicts);
        }
        $this->updateMetaTemplate($metaTemplate, $template, $errors);
        return !empty($errors) ? $errors[0] : true;
    }

    /**
     * Execute the `create_new` update strategy by creating a new meta-template
     * Strategy:
     * - Create a new meta-template
     * - Make the new meta-template `default` and `enabled` if previous template had these states
     * - Turn of these states on the old meta-template
     *
     * @param array $template
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @return bool|string If the new template could be saved or the error message
     */
    public function executeStrategyCreateNew(array $template, \App\Model\Entity\MetaTemplate $metaTemplate)
    {
        if (!$this->canBeSavedWithoutDuplicates($template)) {
            $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']);
        }
        $errors = [];
        $template['is_default'] = $metaTemplate->is_default;
        $template['enabled'] = $metaTemplate->enabled;
        $savedMetaTemplate = null;
        $success = $this->saveNewMetaTemplate($template, $errors, $savedMetaTemplate);
        if ($success) {
            if ($metaTemplate->is_default) {
                $metaTemplate->set('is_default', false);
                $metaTemplate->set('enabled', false);
                $this->save($metaTemplate);
            }
        }
        return !empty($errors) ? $errors[0] : true;
    }

    /**
     * Supersede a meta-fields's meta-template-field with the provided one.
     *
     * @param array $metaFields
     * @param \App\Model\Entity\MetaTemplateField $savedMetaTemplateField
     * @return bool True if the replacement was a success, False otherwise
     */
    public function supersedeMetaFieldsWithMetaTemplateField(array $metaFields,  \App\Model\Entity\MetaTemplate $savedMetaTemplate): bool
    {
        $savedMetaTemplateFieldByName = Hash::combine($savedMetaTemplate['meta_template_fields'], '{n}.field', '{n}');
        foreach ($metaFields as $i => $metaField) {
            $savedMetaTemplateField = $savedMetaTemplateFieldByName[$metaField->field];
            $metaField->set('meta_template_id', $savedMetaTemplateField->meta_template_id);
            $metaField->set('meta_template_field_id', $savedMetaTemplateField->id);
        }
        $entities = $this->MetaTemplateFields->MetaFields->saveMany($metaFields);
        return !empty($entities);
    }

    /**
     * Compute the validity of the provided meta-fields under the provided meta-template
     *
     * @param \App\Model\Entity\MetaField[] $metaFields
     * @param array|\App\Model\Entity\MetaTemplate $template
     * @return \App\Model\Entity\MetaField[] The list of conflicting meta-fields under the provided template
     */
    public function getMetaFieldsConflictsUnderTemplate(array $metaFields, $template): array
    {
        if (!is_array($template) && get_class($template) == 'App\Model\Entity\MetaTemplate') {
            $metaTemplateFields = $template->meta_template_fields;
            $existingMetaTemplate = true;
        } else {
            $metaTemplateFields = $template['metaFields'];
        }
        $conflicting = [];
        $metaTemplateFieldByName = [];
        foreach ($metaTemplateFields as $metaTemplateField) {
            if (!is_array($template)) {
                $metaTemplateField = $metaTemplateField->toArray();
            }
            $metaTemplateFieldByName[$metaTemplateField['field']] = $this->MetaTemplateFields->newEntity($metaTemplateField);
        }
        foreach ($metaFields as $metaField) {
            if (empty($metaTemplateFieldByName[$metaField->field])) { // Meta-field was removed from the template
                $isValid = false;
            } else {
                $isValid = $this->MetaTemplateFields->MetaFields->isValidMetaFieldForMetaTemplateField(
                    $metaField->value,
                    $metaTemplateFieldByName[$metaField->field]
                );
            }
            if ($isValid !== true) {
                $conflicting[] = $metaField;
            }
        }
        return $conflicting;
    }

    /**
     * Compute the potential conflict that would be introduced by updating an existing meta-template-field with the provided one.
     * This will go through all instanciation of the existing meta-template-field and checking their validity against the provided one.
     *
     * @param \App\Model\Entity\MetaTemplateField $metaTemplateField
     * @param array $templateField
     * @return array
     */
    public function computeExistingMetaTemplateFieldConflictForMetaTemplateField(\App\Model\Entity\MetaTemplateField $metaTemplateField, array $templateField, string $scope): array
    {
        $result = [
            'automatically-updateable' => true,
            'conflicts' => [],
            'conflictingEntities' => [],
        ];
        if ($metaTemplateField->multiple && $templateField['multiple'] == false) { // Field is no longer multiple
            $query = $this->MetaTemplateFields->MetaFields->find();
            $query
                ->enableHydration(false)
                ->select([
                    'parent_id',
                    'meta_template_field_id',
                    'count' => $query->func()->count('meta_template_field_id'),
                ])
                ->where([
                    'meta_template_field_id' => $metaTemplateField->id,
                ])
                ->group(['parent_id'])
                ->having(['count >' => 1]);
            $conflictingStatus = $query->all()->toList();
            if (!empty($conflictingStatus)) {
                $result['automatically-updateable'] = false;
                $result['conflicts'][] = __('This field is no longer multiple and is being that way');
                $result['conflictingEntities'] = Hash::extract($conflictingStatus, '{n}.parent_id');
            }
        }

        if (
            (!empty($templateField['regex']) && $templateField['regex'] != $metaTemplateField->regex) ||
            !empty($templateField['values_list'])
        ) {
            $entities = $this->getEntitiesForMetaTemplateField($scope, $metaTemplateField->id, true);
            $conflictingEntities = [];
            foreach ($entities as $entity) {
                foreach ($entity['meta_fields'] as $metaField) {
                    $isValid = $this->MetaTemplateFields->MetaFields->isValidMetaFieldForMetaTemplateField(
                        $metaField->value,
                        $templateField
                    );
                    if ($isValid !== true) {
                        $conflictingEntities[] = [
                            'parent_id' => $entity->id,
                            'meta_template_field_id' => $metaTemplateField->id,
                        ];
                        break;
                    }
                }
            }

            if (!empty($conflictingEntities)) {
                $result['automatically-updateable'] = $result['automatically-updateable'] && false;
                $result['conflicts'][] = __('This field is instantiated with values not passing the validation anymore.');
                $result['conflictingEntities'] = array_merge($result['conflictingEntities'], $conflictingEntities);
            }
        }
        return $result;
    }

    /**
     * Return all entities having the meta-fields using the provided meta-template-field.
     *
     * @param string $scope
     * @param integer $metaTemplateFieldID The ID of the matching meta-template-field
     * @param boolean $includeMatchingMetafields Should the entities also include the matching meta-fields
     * @return array
     */
    private function getEntitiesForMetaTemplateField(string $scope, int $metaTemplateFieldID, bool $includeMatchingMetafields=true): array
    {
        $entitiesTable = $this->getTableForMetaTemplateScope($scope);
        $entitiesWithMetaFieldQuery = $this->MetaTemplateFields->MetaFields->find();
        $entitiesWithMetaFieldQuery
            ->enableHydration(false)
            ->select([
                'parent_id',
            ])
            ->where([
                'meta_template_field_id' => $metaTemplateFieldID,
            ]);

        $entitiesQuery = $entitiesTable->find()
            ->where(['id IN' => $entitiesWithMetaFieldQuery]);

        if ($includeMatchingMetafields) {
            $entitiesQuery->contain([
                'MetaFields' => [
                    'conditions' => [
                        'MetaFields.meta_template_field_id' => $metaTemplateFieldID,
                    ]
                ]
            ]);
        }

        return $entitiesQuery->all()->toList();
    }

    /**
     * Check the conflict that would be introduced if the metaTemplate would be updated to the provided template
     *
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @param \App\Model\Entity\MetaTemplate|array $template
     * @return array
     */
    public function getMetaTemplateConflictsForMetaTemplate(\App\Model\Entity\MetaTemplate $metaTemplate, $template): array
    {
        $templateMetaFields = [];
        if (!is_array($template) && get_class($template) == 'App\Model\Entity\MetaTemplate') {
            $templateMetaFields = $template->meta_template_fields;
        } else {
            $templateMetaFields = $template['metaFields'];
        }
        $conflicts = [];
        $existingMetaTemplateFields = Hash::combine($metaTemplate->toArray(), 'meta_template_fields.{n}.field', 'meta_template_fields.{n}');
        foreach ($templateMetaFields as $newMetaField) {
            foreach ($metaTemplate->meta_template_fields as $metaField) {
                if ($newMetaField['field'] == $metaField->field) {
                    unset($existingMetaTemplateFields[$metaField->field]);
                    $metaFieldArray = !is_array($newMetaField) && get_class($newMetaField) == 'App\Model\Entity\MetaTemplateField' ? $newMetaField->toArray() : $newMetaField;
                    $templateConflictsForMetaField = $this->computeExistingMetaTemplateFieldConflictForMetaTemplateField($metaField, $metaFieldArray, $metaTemplate->scope);
                    if (!$templateConflictsForMetaField['automatically-updateable']) {
                        $conflicts[$metaField->field] = $templateConflictsForMetaField;
                        $conflicts[$metaField->field]['existing_meta_template_field'] = $metaField;
                        $conflicts[$metaField->field]['existing_meta_template_field']['conflicts'] = $templateConflictsForMetaField['conflicts'];
                    }
                }
            }
        }
        if (!empty($existingMetaTemplateFields)) {
            foreach ($existingMetaTemplateFields as $metaTemplateField) {
                $query = $this->MetaTemplateFields->MetaFields->find();
                $query
                    ->enableHydration(false)
                    ->select([
                        'parent_id',
                        'meta_template_field_id',
                    ])
                    ->where([
                        'meta_template_field_id' => $metaTemplateField['id'],
                    ])
                    ->group(['parent_id']);
                $entityWithMetafieldToBeRemoved = $query->all()->toList();
                $conflicts[$metaTemplateField['field']] = [
                    'automatically-updateable' => false,
                    'conflicts' => [__('This field is intended to be removed')],
                    'conflictingEntities' => $entityWithMetafieldToBeRemoved,
                ];
            }
        }
        return $conflicts;
    }

    /**
     * Get update status for the latest meta-template in the database for the provided template
     *
     * @param array $template
     * @param \App\Model\Entity\MetaTemplate $metaTemplate $metaTemplate
     * @return array 
     */
    public function getUpdateStatusForTemplate(array $template): array
    {
        $updateStatus = [
            'new' => true,
            'up-to-date' => false,
            'automatically-updateable' => false,
            'conflicts' => [],
            'template' => $template,
        ];
        $query = $this->find()
            ->contain('MetaTemplateFields')
            ->where([
                'uuid' => $template['uuid'],
            ])
            ->order(['version' => 'DESC']);
        $metaTemplate = $query->first();
        if (!empty($metaTemplate)) {
            $updateStatus = array_merge(
                $updateStatus,
                $this->getStatusForMetaTemplate($template, $metaTemplate)
            );
        }
        return $updateStatus;
    }

    /**
     * Get update status for the meta-template stored in the database and the provided template
     *
     * @param array $template
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @return array
     */
    public function getStatusForMetaTemplate(array $template, \App\Model\Entity\MetaTemplate $metaTemplate): array
    {
        $updateStatus = [];
        $updateStatus['existing_template'] = $metaTemplate;
        $updateStatus['current_version'] = $metaTemplate->version;
        $updateStatus['next_version'] = $template['version'];
        $updateStatus['new'] = false;
        $updateStatus['automatically-updateable'] = false;
        $updateStatus['conflicts'] = [];
        if (empty($template)) {
            $updateStatus['up-to-date'] = false;
            $updateStatus['automatically-updateable'] = false;
            $updateStatus['can-be-removed'] = false;
            return $updateStatus;
        }
        if (intval($metaTemplate->version) >= intval($template['version'])) {
            $updateStatus['up-to-date'] = true;
            $updateStatus['conflicts'][] = __('Could not update the template. Local version is equal or newer.');
            return $updateStatus;
        }

        $conflicts = $this->getMetaTemplateConflictsForMetaTemplate($metaTemplate, $template);
        if (!empty($conflicts)) {
            $updateStatus['conflicts'] = $conflicts;
            $updateStatus['automatically-updateable'] = false;
            $emptySum = 0;
            foreach ($conflicts as $fieldname => $fieldStatus) {
                if (!empty($fieldStatus['conflictingEntities'])) {
                    break;
                }
                $emptySum += 1;
            }
            if ($emptySum == count($conflicts)) {
                $updateStatus['automatically-updateable'] = true;
            }
        } else {
            $updateStatus['automatically-updateable'] = true;
        }
        $updateStatus['meta_field_amount'] = $this->MetaTemplateFields->MetaFields->find()->where(['meta_template_id' => $metaTemplate->id])->count();
        $updateStatus['can-be-removed'] = empty($updateStatus['meta_field_amount']) && empty($updateStatus['to-existing']);
        return $updateStatus;
    }

    /**
     * Massages the meta-fields of an entity based on the input
     * - If the keyed ID of the input meta-field is new, a new meta-field entity is created
     * - If the input meta-field's value is empty for an existing meta-field, the existing meta-field is marked as to be deleted
     * - If the input meta-field already exists, patch the entity and attach the validation errors
     *
     * @param \App\Model\Entity\AppModel $entity
     * @param array $input
     * @param \App\Model\Entity\MetaTemplate $metaTemplate
     * @return array An array containing the entity with its massaged meta-fields and the meta-fields that should be deleted
     */
    public function massageMetaFieldsBeforeSave(\App\Model\Entity\AppModel $entity, array $input, \App\Model\Entity\MetaTemplate $metaTemplate): array
    {
        $metaFieldsTable = $this->MetaTemplateFields->MetaFields;
        $className = Inflector::camelize(Inflector::pluralize($metaTemplate->scope));
        $entityTable = TableRegistry::getTableLocator()->get($className);
        $metaFieldsIndex = [];
        if (!empty($entity->meta_fields)) {
            foreach ($entity->meta_fields as $i => $metaField) {
                $metaFieldsIndex[$metaField->id] = $i;
            }
        } else {
            $entity->meta_fields = [];
        }

        $metaFieldsToDelete = [];
        foreach ($input['MetaTemplates'] as $template_id => $template) {
            foreach ($template['meta_template_fields'] as $meta_template_field_id => $meta_template_field) {
                $rawMetaTemplateField = $metaTemplate->meta_template_fields[$meta_template_field_id];
                foreach ($meta_template_field['metaFields'] as $meta_field_id => $meta_field) {
                    if ($meta_field_id == 'new') { // create new meta_field
                        $new_meta_fields = $meta_field;
                        foreach ($new_meta_fields as $new_value) {
                            if (!empty($new_value)) {
                                $metaField = $metaFieldsTable->newEmptyEntity();
                                $metaFieldsTable->patchEntity($metaField, [
                                    'value' => $new_value,
                                    'scope' => $entityTable->getBehavior('MetaFields')->getScope(),
                                    'field' => $rawMetaTemplateField->field,
                                    'meta_template_id' => $rawMetaTemplateField->meta_template_id,
                                    'meta_template_field_id' => $rawMetaTemplateField->id,
                                    'parent_id' => $entity->id,
                                    'uuid' => Text::uuid(),
                                ]);
                                $entity->meta_fields[] = $metaField;
                                $entity->MetaTemplates[$template_id]->meta_template_fields[$meta_template_field_id]->metaFields[] = $metaField;
                            }
                        }
                    } else {
                        $new_value = $meta_field['value'];
                        if (!empty($new_value)) { // update meta_field and attach validation errors
                            if (!empty($metaFieldsIndex[$meta_field_id])) {
                                $index = $metaFieldsIndex[$meta_field_id];
                                $metaFieldsTable->patchEntity($entity->meta_fields[$index], [
                                    'value' => $new_value, 'meta_template_field_id' => $rawMetaTemplateField->id
                                ], ['value']);
                                $metaFieldsTable->patchEntity(
                                    $entity->MetaTemplates[$template_id]->meta_template_fields[$meta_template_field_id]->metaFields[$meta_field_id],
                                    ['value' => $new_value, 'meta_template_field_id' => $rawMetaTemplateField->id],
                                    ['value']
                                );
                            } else { // metafield comes from a second POST where the temporary entity has already been created
                                $metaField = $metaFieldsTable->newEmptyEntity();
                                $metaFieldsTable->patchEntity($metaField, [
                                    'value' => $new_value,
                                    'scope' => $entityTable->getBehavior('MetaFields')->getScope(), // get scope from behavior
                                    'field' => $rawMetaTemplateField->field,
                                    'meta_template_id' => $rawMetaTemplateField->meta_template_id,
                                    'meta_template_field_id' => $rawMetaTemplateField->id,
                                    'parent_id' => $entity->id,
                                    'uuid' => Text::uuid(),
                                ]);
                                $entity->meta_fields[] = $metaField;
                                $entity->MetaTemplates[$template_id]->meta_template_fields[$meta_template_field_id]->metaFields[] = $metaField;
                            }
                        } else { // Metafield value is empty, indicating the field should be removed
                            $index = $metaFieldsIndex[$meta_field_id];
                            $metaFieldsToDelete[] = $entity->meta_fields[$index];
                            unset($entity->meta_fields[$index]);
                            unset($entity->MetaTemplates[$template_id]->meta_template_fields[$meta_template_field_id]->metaFields[$meta_field_id]);
                        }
                    }
                }
            }
        }

        $entity->setDirty('meta_fields', true);
        return ['entity' => $entity, 'metafields_to_delete' => $metaFieldsToDelete];
    }

    /**
     * Apply transformTemplatesInputsForUnchangedSupport to a list of metatemplates
     *
     * @param MetaTemplate $template The form template definition to transform.
     * @return MetaTemplate The transformed template with updated input types.
     */
    public function transformTemplatesInputsForUnchangedSupport(array $metaTemplates): array
    {
        return array_map([$this, 'transformTemplateInputsForUnchangedSupport'], $metaTemplates);
    }

    /**
     * Transforms binary inputs (e.g., checkboxes and selects) in the template
     * into tristate selects that support 'true', 'false', and 'unchanged' values.
     *
     * This is used to distinguish between explicit false/true values and
     * inputs that were not modified by the user.
     *
     * @param MetaTemplate $template The form template definition to transform.
     * @return MetaTemplate The transformed template with updated input types.
     */
    public function transformTemplateInputsForUnchangedSupport(MetaTemplate $metaTemplate): MetaTemplate
    {
        foreach ($metaTemplate['meta_template_fields'] as $i => &$meta_template_field) {
            if ($meta_template_field['form_type'] == 'checkbox') {
                $meta_template_field->default_value = 'unchanged';
                $meta_template_field->skip_no_value = true;
                $meta_template_field['form_type'] = 'select';
                $meta_template_field['values_list'] = [
                    ['value' => true, 'text' => __('Active')],
                    ['value' => false, 'text' => __('Inactive')],
                    ['value' => 'unchanged', 'text' => __('-- Unchanged --')],
                ];
            } else if ($meta_template_field['form_type'] == 'select') {
                $meta_template_field['values_list'][] = ['value' => 'unchanged', 'text' => __('-- Unchanged --')];
            }
        }
        return $metaTemplate;
    }
}

class UpdateError
{
    public $success;
    public $message = '';
    public $errors = [];

    public function __construct($success = false, $message = '', $errors = [])
    {
        $this->success = $success;
        $this->message = $message;
        $this->errors = $errors;
    }
}
