<?php


namespace AutoAdmin\Bundle\Content\Translation;

use AutoAdmin\Bundle\Content\Entity\AbstractContentTranslation;
use AutoAdmin\Bundle\Content\Entity\Locale;
use AutoAdmin\Bundle\Content\Model\LocaleInterface;
use AutoAdmin\Bundle\Content\Model\TranslatableInterface;
use AutoAdmin\Bundle\Content\Model\TranslationInterface;
use AutoAdmin\Bundle\Content\Site\SiteContextHelper;
use ReflectionClass;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Contracts\Service\Attribute\Required;

class EntityTranslator
{
    /**
     * @var TranslatableInterface[]
     */
    private array $managed = [];
    private ?LocaleInterface $locale = null;

    /**
     * @var LocaleInterface[]
     */
    private array $fallbackLocales = [];

    protected SiteContextHelper $siteContextHelper;

    public function __construct(
        protected PropertyAccessorInterface $pa,
    ) {
    }

    #[Required]
    public function inject(SiteContextHelper $siteContextHelper): void
    {
        $this->siteContextHelper = $siteContextHelper;
    }

    public function translate(TranslatableInterface $entity, string $field, ?LocaleInterface $locale = null)
    {
        if ($locale === null) {
            $locale = $this->getLocale();
        }

        return $this->getPropertyValue($entity, $field, $locale);
    }

    /**
     * @param object $object
     * @param $propertyPath
     * @param Locale $locale
     * @return mixed|null
     */
    private function getPropertyValue(object $object, $propertyPath, Locale $locale): mixed
    {
        $stepsSubjects = $this->buildPropertyPathStepSubjects($object, $propertyPath, $locale);

        // chances are we want the last bit, but we can also work with previous path "steps" in $stepsSubjects
        return $stepsSubjects[array_key_last($stepsSubjects)];
    }

    /*
     * Split propertyPath by step and associate translated fields values to path steps
     * e.g.: "category.parent._tr.slug" in autofill parameters
     *
     * // example payload
     * // array:4 [▼
     * //   "category" => App\Entity\ProductCategory
     * //   "parent" => App\Entity\ProductCategory
     * //   "translations[1]" => App\Entity\ProductCategoryTranslation
     * //   "slug" => "urban-furniture"
     * // ]
     */
    private function buildPropertyPathStepSubjects(object $object, $propertyPath, Locale $locale): array
    {
        $lastNode = $object;
        $stepsSubjects = [];
        foreach (explode('.', $propertyPath) as $idx => $fieldStep) {
            if (empty($lastNode)) {
                break;
            }
            $transIndex = false;
            if ($fieldStep === 'currentTranslation') {
                $fieldStep = sprintf('translations[%s]', $transIndex = $this->getTranslationIndex($lastNode, $locale));
                if ($transIndex === null) {
                    // we may want to handle uneven translations (only one language created but another asked in routing)

//                    dump($object);
//                    dump($propertyPath);
//                    die;
                }
            }

            $stepsSubjects[$fieldStep] = $transIndex === null ? null : $this->pa->getValue($lastNode, $fieldStep);

            $lastNode = $stepsSubjects[$fieldStep];
        }

        return $stepsSubjects;
    }

    public function getLocale(): ?LocaleInterface
    {
        return $this->locale;
    }

    public function setLocale(string|LocaleInterface $locale): void
    {
        if (is_string($locale)) {
            $locale = new Locale($locale);
        }

        $this->locale = $locale;

        $this->flush();
    }

    public function getTranslation(TranslatableInterface $entity, LocaleInterface $locale): ?TranslationInterface
    {
        $found = null;
        foreach ($entity->getTranslations() as $translation) {
            if ($translation->getLocale()->getCode() === $locale->getCode()) {
                $found = $translation;
            }
        }

        return $found;
    }

    public function getTranslationIndex(TranslatableInterface $entity, LocaleInterface $locale): ?int
    {
        $found = null;

        foreach ($entity->getTranslations() as $idx => $translation) {
            if ($translation->getLocale()->getCode() === $locale->getCode()) {
                $found = $idx;
                break;
            }
        }

        return $found;
    }

    private function flush(): void
    {
        foreach ($this->managed as $entity) {
            $this->initializeCurrentTranslation($entity);
        }
    }

    public function initializeCurrentTranslation(TranslatableInterface $entity): string|LocaleInterface|null
    {
        $currentLocale = $this->getLocale();
        $this->initializeTranslation($entity, $currentLocale);

        return $currentLocale;
    }

    public function initializeCurrentTranslationFromContext(TranslatableInterface $entity): string|LocaleInterface|null
    {
        $currentLocale = $this->siteContextHelper->getLocale();
        $this->initializeTranslation($entity, $currentLocale);

        return $currentLocale;
    }

    /**
     * Load localized translation in translatable.currentTranslation using given locale
     *
     * @param TranslatableInterface $entity
     * @param LocaleInterface|null $locale
     * @return bool
     */
    public function initializeTranslation(TranslatableInterface $entity, LocaleInterface $locale = null): bool
    {
        if ($locale === null) {
            $locale = (new Locale('fr'));
        }
        $this->managed[$this->getId($entity)] = $entity;

        $translation = $this->getTranslation($entity, $locale);
        $entity->setCurrentTranslation($translation);

        return $translation !== null;
    }

    /**
     * Permits to generate a localized route from an original content in another locale.
     * Performance won't be the best because cloning negates routing's spl-id-indexed cache table.
     *
     * @param TranslatableInterface $entity
     * @param LocaleInterface $locale
     * @return TranslatableInterface
     */
    public function initializeTranslationInClone(
        TranslatableInterface $entity,
        LocaleInterface $locale
    ): TranslatableInterface {
        $localized = clone $entity;
        $this->initializeTranslation($localized, $locale);

        return $localized;
    }

    private function getId(TranslatableInterface $translatable): string
    {
        return spl_object_hash($translatable);
    }

    public function detach(TranslatableInterface $translatable): void
    {
        unset($this->managed[$this->getId($translatable)]);
    }


    public function fieldExistsInEntity($entity, string $field): bool
    {
        $reflection = new ReflectionClass($entity);
        $properties = $reflection->getProperties();
        $propertyNames = [];
        foreach ($properties as $property) {
            $propertyNames[] = $property->getName();
        }

        return in_array($field, $propertyNames);
    }

    private function initializeFallbackTranslation(TranslatableInterface $entity): ?string
    {
        $fallbackLocales = $this->getFallbackLocales();
        foreach ($fallbackLocales as $fallback) {
            if ($this->initializeTranslation($entity, $fallback)) {
                return $fallback;
            }
        }

        return null;
    }

    /**
     * @return string[]
     */
    public function getFallbackLocales(): array
    {
        return $this->fallbackLocales;
    }

    /**
     * @param LocaleInterface[] $fallbackLocales
     */
    public function setFallbackLocales(array $fallbackLocales): void
    {
        $this->fallbackLocales = $fallbackLocales;
    }

    public function getTranslatableFieldsFromClass(string $class): array
    {
        $classTranslation = $class.'Translation';
        if (!class_exists($classTranslation)) {
            return [];
        }

        $reflection = new ReflectionClass($classTranslation);
        $properties = $reflection->getProperties();

        $props = [];

        foreach ($properties as $key => $property) {
            if ($property->class === $classTranslation || $property->class === AbstractContentTranslation::class) {
                $props[$key] = $property;
            }
        }

        return $props;
    }
}
