<?php


namespace AutoAdmin\Bundle\Content\Doctrine\EventListener;


use AutoAdmin\Bundle\Content\Model\TranslatableInterface;
use AutoAdmin\Bundle\Content\Model\TranslationInterface;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Mapping\MappingException;

class TranslationMetadataSubscriber implements EventSubscriber
{
    const TRANSLATION_CLASS_SUFFIX = 'Translation';

    public function getSubscribedEvents(): array
    {
        return [
            Events::loadClassMetadata,
        ];
    }

    public function loadClassMetadata(LoadClassMetadataEventArgs $args)
    {
        $metadata = $args->getClassMetadata();
        $class = $metadata->getName();
//        dump($class);

        if (
            is_subclass_of($class, TranslatableInterface::class)
            && !$metadata->getReflectionClass()->isAbstract()
        ) {
            $this->mapOneToMany($metadata);
        }

        if (
            is_subclass_of($metadata->getName(), TranslationInterface::class)
            && !$metadata->getReflectionClass()->isAbstract()
        ) {
            $this->mapManyToOne($metadata);
        }
    }

    /**
     * Annotation example:
     * @ ORM\OneToMany(targetEntity="ArticleTranslation", mappedBy="translatable", cascade={"ALL"}, orphanRemoval=true)
     *
     * Mapping example:
     *
     * +associationMappings: array:1 [
     *  "translations" => array:15 [
     *    "fieldName" => "translations"
     *    "mappedBy" => "translatable"
     *    "targetEntity" => "App\Entity\ArticleTranslation"
     *    "cascade" => array:5 [
     *      0 => "remove"
     *      1 => "persist"
     *      2 => "refresh"
     *      3 => "merge"
     *      4 => "detach"
     *    ]
     *    "orphanRemoval" => true
     *    "fetch" => 2
     *    "type" => 4
     *    "inversedBy" => null
     *    "isOwningSide" => false
     *    "sourceEntity" => "App\Entity\Article"
     *    "isCascadeRemove" => true
     *    "isCascadePersist" => true
     *    "isCascadeRefresh" => true
     *    "isCascadeMerge" => true
     *    "isCascadeDetach" => true
     *  ]
     * ]
     */
    protected function mapOneToMany(ClassMetadata $metadata): ClassMetadata
    {
        $className = $metadata->getName();
        $associationField = 'translations';

        try {
            $existingMapping = $metadata->getAssociationMapping('translations');
        } catch (MappingException $e) {
            $existingMapping = [];
        }

        $translationEntityClassName = $className . self::TRANSLATION_CLASS_SUFFIX;

        $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];

        $translationAssociation = [
            'fieldName' => 'translations',
            'mappedBy' => 'translatable',
            'targetEntity' => $translationEntityClassName,
            'orphanRemoval' => true,
            'fetch' => ClassMetadataInfo::FETCH_LAZY,
            'type' => ClassMetadataInfo::ONE_TO_MANY,
            'inversedBy' => null,
            'isOwningSide' => false,
            'sourceEntity' => $className,
            'realClass' => $metadata->getName(),
        ];
        $translationAssociation = $this->processCascading($translationAssociation, $allCascades);

        // merge with existing metadata
        $translationAssociation = array_merge($translationAssociation, $existingMapping);

        return $this->replaceMapping($metadata, $associationField, $translationAssociation);
    }

    /*
     * Mapping example:
     *
     *   +associationMappings: array:2 [
     *       "translatable" => array:20 [
     *             "fieldName" => "translatable"
     *             "id" => true
     *             "joinColumns" => array:1 [
     *               0 => array:2 [
     *                 "name" => "translatable_id"
     *                 "referencedColumnName" => "id"
     *               ]
     *             ]
     *             "cascade" => []
     *             "inversedBy" => "translations"
     *             "targetEntity" => "App\Entity\Article"
     *             "fetch" => 2
     *             "type" => 2
     *             "mappedBy" => null
     *             "isOwningSide" => true
     *             "sourceEntity" => "App\Entity\ArticleTranslation"
     *             "isCascadeRemove" => false
     *             "isCascadePersist" => false
     *             "isCascadeRefresh" => false
     *             "isCascadeMerge" => false
     *             "isCascadeDetach" => false
     *             "sourceToTargetKeyColumns" => array:1 [
     *               "translatable_id" => "id"
     *             ]
     *             "joinColumnFieldNames" => array:1 [
     *               "translatable_id" => "translatable_id"
     *             ]
     *             "targetToSourceKeyColumns" => array:1 [
     *               "id" => "translatable_id"
     *             ]
     *             "orphanRemoval" => false
     *       ]
     *     ]
     */

    /**
     * Code from \Doctrine\ORM\Mapping\ClassMetadataInfo::_validateAndCompleteAssociationMapping
     */
    protected function processCascading(array $mapping, $allCascades): array
    {
        // Cascades
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : ['all'];

        if (in_array('all', $cascades)) {
            $cascades = $allCascades;
        }

        $mapping['cascade'] = $cascades;
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);

        return $mapping;
    }

    private function replaceMapping(ClassMetadata $metadata, $field, $mapping): ClassMetadata
    {
        try {
            $metadata->addInheritedAssociationMapping($mapping);
        } catch (MappingException $e) {
            $metadata->setAssociationOverride($field, $mapping);
        }

        return $metadata;
    }

    protected function mapManyToOne(ClassMetadata $metadata): ClassMetadata
    {
//        if ($metadata->getName() === PageTranslation::class) {
//            dump($metadata);
//            die;
//        }

        $metadata = $this->replaceMapping(
            $metadata,
            'translatable',
            [
                'fieldName' => 'translatable',
                'id' => true,
                'joinColumns' => [
                    [
                        'name' => 'translatable_id',
                        'referencedColumnName' => 'id',
                        'onDelete' => "CASCADE",
                    ],
                ],
                'cascade' => [],
                'inversedBy' => 'translations',
                'targetEntity' =>
                    substr($metadata->getName(), 0, -1 * strlen(self::TRANSLATION_CLASS_SUFFIX)),
                'fetch' => ClassMetadataInfo::FETCH_LAZY,
                'type' => ClassMetadataInfo::MANY_TO_ONE,
                'mappedBy' => null,
                'isOwningSide' => true,
                'sourceEntity' => $metadata->getName(),
                'isCascadeRemove' => true,
                'isCascadePersist' => true,
                'isCascadeRefresh' => true,
                'isCascadeMerge' => true,
                'isCascadeDetach' => true,
                'sourceToTargetKeyColumns' => ['translatable_id' => 'id',],
                'joinColumnFieldNames' => ['translatable_id' => 'translatable_id',],
                'targetToSourceKeyColumns' => ['id' => 'translatable_id',],
                'orphanRemoval' => false,
            ]
        );

//        if ($metadata->getName() === 'AutoAdmin\Bundle\Content\Entity\TranslationTranslation') {
//            dump($metadata);
//        }

        return $metadata;
    }
}
