<?php
namespace Newland\Toubiz\Sync\Neos\Importer;

/*
 * This file is part of the "toubiz-sync-neos" package.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Newland\Toubiz\Api\Constants\Language;
use Newland\Toubiz\Sync\Neos\Domain\Model\AbstractEntity;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;

abstract class AbstractImporter implements LoggerInterface
{
    use LoggerWrapperWithPredefinedContext;

    /**
     * @Flow\Inject()
     * @var PersistenceManagerInterface
     */
    protected $persistenceManager;

    /**
     * @var mixed
     */
    protected $data;

    /**
     * @var string
     */
    protected $language;

    /**
     * @var array
     * @Flow\InjectConfiguration()
     */
    protected $packageConfig;

    /**
     * @param LoggerAwareInterface|mixed $data
     */
    protected function initializeLogger($data, array $context): void
    {
        $this->context = $this->mergeLoggingContext($context);
        if ($data instanceof LoggerAwareInterface || method_exists($data, 'setLogger')) {
            $data->setLogger($this);
        }
    }

    public function setLanguage(?string $language): void
    {
        if ($language === null) {
            return;
        }

        Language::throwIfInvalid($language);
        $this->language = $language;
    }

    /**
     * Main import method.
     *
     * Must be defined in each implementation.
     *
     * @param mixed $data
     * @return mixed
     */
    abstract public function import($data);

    /**
     * Returns a DateTime instance for given object.
     *
     * @param mixed $object
     * @return \DateTime
     */
    protected function asDateTime($object): \DateTime
    {
        if (is_string($object)) {
            return new \DateTime($object);
        }

        return $object;
    }

    /**
     * Converts date and time strings into a DateTime object.
     *
     * @param string $date A date in YYYY-MM-DD format
     * @param string $time A time in hh:mm:ss format
     * @return \DateTime
     */
    protected function buildDateTimeFromStrings($date, $time = ''): \DateTime
    {
        $object = new \DateTime($date);
        if ($time) {
            [$hour, $minute] = explode(':', $time);
            $object->setTime((int) $hour, (int) $minute);
        }

        return $object;
    }

    /** @param Collection<int, mixed> $collection */
    protected function updateCollection(Collection $collection, array $data, callable $generator): array
    {
        $generated = [];
        foreach ($data as $row) {
            $value = $generator($row);
            if ($value) {
                $generated[] = $value;
            }
        }

        return $this->setCollectionValues($collection, $generated);
    }

    /** @param Collection<int, mixed> $collection */
    protected function setCollectionValues(Collection $collection, array $newValues): array
    {
        $new = new ArrayCollection($newValues);
        $removed = [];
        $added = [];

        // Remove old elements
        foreach ($collection as $element) {
            // Every element needs to be manually removed and re-added to enforce sorting later
            // (even if no element has changed) because Collection have no persisted sorting.
            $collection->removeElement($element);
            if (!$new->contains($element)) {
                $removed[] = $element;
            }
        }

        // Add new ones
        foreach ($new as $element) {
            if (!$collection->contains($element)) {
                $collection->add($element);
                $added[] = $element;
            }
        }

        return [ $collection, $added, $removed ];
    }
}
