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

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

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Core\Booting\Scripts;
use Neos\Flow\Log\PsrLoggerFactoryInterface;
use Neos\Flow\Mvc\Exception\StopActionException;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Neos\Flow\Validation\ValidatorResolver;
use Newland\NeosCommon\Service\ObjectPathMappingService;
use Newland\Toubiz\Api\Constants\Language;
use Psr\Log\LoggerInterface;

class AbstractCommandController extends \Newland\NeosCommon\Command\AbstractCommandController
{
    /**
     * Number of entities after which the current EntityManager state should be cleaned.
     *
     * Smaller sizes may decrease memory footprint but increase runtime,
     * Larger sizes may increase memory footprint but decrease runtime as cached
     * values can be used instead of refetching them.
     *
     * @var int
     */
    protected $cleanStateAfter = 20;

    /**
     * @Flow\InjectConfiguration
     * @var array
     */
    protected $configuration;

    /**
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @var ObjectPathMappingService
     * @Flow\Inject()
     */
    protected $objectPathMappingService;

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

    /**
     * @var ValidatorResolver
     * @Flow\Inject()
     */
    protected $validatorResolver;

    /**
     * @Flow\InjectConfiguration(package="Neos.Flow")
     * @var array
     */
    protected $flowSettings;

    public function __construct()
    {
        parent::__construct();

        $this->on('finish', function () {
            $this->outputLine('Flushing cache');
            Scripts::executeCommand('neos.flow:cache:flush', $this->flowSettings);
            $this->outputLine('Warming up cache');
            Scripts::executeCommand('neos.flow:cache:warmup', $this->flowSettings);
        });
    }

    public function injectLogger(PsrLoggerFactoryInterface $loggerFactory)
    {
        $this->logger = $loggerFactory->get('newlandSyncImport');
    }

    /**
     * Find configuration for given service string.
     *
     * @param string $service
     * @return array|null
     */
    protected function getConfigurationForService($service)
    {
        if (array_key_exists('services', $this->configuration)
            && array_key_exists($service, $this->configuration['services'])
        ) {
            $configuration = $this->configuration['services'][$service];
            $configuration['languages'] = $configuration['languages'] ?? [ Language::DE ];
            return $configuration;
        }

        return null;
    }

    /**
     * @param mixed $configuration
     * @return bool
     */
    protected function hasClients($configuration): bool
    {
        return $this->hasArrayChild($configuration, 'clients');
    }

    /**
     * @param mixed $array
     * @param string $key
     * @return bool
     */
    protected function hasArrayChild($array, string $key): bool
    {
        return is_array($array) && array_key_exists($key, $array) &&
            is_array($array[$key]);
    }

    /**
     * @param array $configuration
     * @return array
     */
    protected function getClients(array $configuration = null)
    {
        if ($configuration === null) {
            return [];
        }
        return $configuration['clients'];
    }

    protected function wrapImportClosure(callable $block): callable
    {
        $calls = 0;
        return function () use ($block, &$calls) {
            try {
                $block(...\func_get_args());
                $this->persistenceManager->persistAll();
                if (++$calls % $this->cleanStateAfter === 0) {
                    $this->persistenceManager->clearState();
                    $this->validatorResolver->reset();
                }
            } catch (\Exception $e) {
                $this->logger->critical(
                    'Error importing record: ' . $e->getMessage(),
                    [
                        'file' => $e->getFile(),
                        'line' => $e->getLine(),
                        'trace' => $e->getTraceAsString(),
                    ]
                );
            }
        };
    }

    protected function askForConfirmationAndAbortIfNoneGiven(string $message, bool $default = false)
    {
        $options = $default ? '[Y|n]' : '[y|N]';
        $message .= ' ' . $options . ' ';
        if (!$this->output->askConfirmation($message, $default)) {
            $this->output->outputLine('Aborting: Action not confirmed');
            throw new StopActionException('Action not confirmed.');
        }
    }

    /**
     * @Flow\Signal()
     * @param string $type
     * @param array $payload
     */
    protected function emitFlush(string $type, array $payload = [])
    {
        $this->callListeners('flush', $type);
    }

    /**
     * @Flow\Signal()
     */
    protected function emitFinish(): void
    {
        $this->callListeners('finish');
    }
}
