<?php
declare(strict_types=1);

namespace Uhura\Grossmarkt\ViewHelpers\Resource; // phpcs:ignore

use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
use TYPO3\CMS\Extbase\Reflection\Exception\PropertyNotAccessibleException;
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;

/**
 * Class RecordsViewHelper
 */
class RecordsViewHelper extends AbstractViewHelper
{
    public static function getContainer(): ContainerInterface
    {
        return GeneralUtility::getContainer();
    }

    /**
     * initialize arguments
     */
    public function initializeArguments(): void
    {
        $this->registerArgument('type', 'string', 'Object type to fetch', true);
        $this->registerArgument('uidList', 'string', 'List of identifiers to fetch', false, '');
        $this->registerArgument('pid', 'string', 'Start point to fetch records', false, '');
        $this->registerArgument('ignoreEnableFields', 'bool', 'Ignore enable fields in query', false, false);
        $this->registerArgument('respectSysLanguage', 'bool', 'Respect system language in query', false, true);
        $this->registerArgument('ignoreTimeRestrictions', 'bool', 'Ignore "starttime","endtime" fields while fetching records. "ignoreEnableFields" is automatically set to "true")', false, false);
        $this->registerArgument('limit', 'int', 'Records limit', false, 0);
        $this->registerArgument('parentCategoryUid', 'string', 'Parent category uid for return subcategories', false, 1);
    }

    /**
     * Fetch multiple records by type
     *
     * Example:
     *      {singleNewsIds -> s:resource.records(type:'GeorgRinger\News\Domain\Model\News')}
     *
     * @param array $arguments
     * @param \Closure $renderChildrenClosure
     * @param RenderingContextInterface $renderingContext
     *
     * @return mixed
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     * @throws PropertyNotAccessibleException
     */
    public static function renderStatic(
        array $arguments,
        \Closure $renderChildrenClosure,
        RenderingContextInterface $renderingContext
    ): mixed
    {
        $uidList = $arguments['uidList'];

        if (empty($uidList)) {
            $uidList = $renderChildrenClosure();
        }

        $identifiers = GeneralUtility::trimExplode(',', $uidList, true);

        if (empty($identifiers) && empty($arguments['pid'])) {
            return [];
        }

        $records = static::fetchRecords($arguments, $identifiers);

        static::sortRecordsByIdentifiersOrder($records, $identifiers);

        return $records;
    }

    /**
     * @param array $arguments
     * @param array $identifiers
     *
     * @return array
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    protected static function fetchRecords(array $arguments, array $identifiers): array
    {
        $query = static::prepareQueryForType($arguments);

        if ($arguments['limit']) {
            $query->setLimit($arguments['limit']);
        }

        $constraints = [];

        if ($arguments['pid']) {
            $constraints[] = $query->equals('pid', $arguments['pid']);
        } else {
            foreach ($identifiers as $uid) {
                $constraints[] = $query->equals('uid', $uid);
            }
        }

        if ($arguments['parentCategoryUid']) {
            $constraints[] = $query->equals('parent', $arguments['parentCategoryUid']);

            return $query->matching($query->logicalAnd(...$constraints))->execute()->toArray();
        }

        return $query->matching($query->logicalOr(...$constraints))->execute()->toArray();
    }

    /**
     * @param array $arguments
     *
     * @return QueryInterface
     * @throws ContainerExceptionInterface
     * @throws NotFoundExceptionInterface
     */
    protected static function prepareQueryForType(array $arguments): QueryInterface
    {
        $query = self::getContainer()->get(PersistenceManager::class)->createQueryForType($arguments['type']);
        $query->getQuerySettings()->setRespectStoragePage(false);
        $query->getQuerySettings()->setIgnoreEnableFields($arguments['ignoreEnableFields']);
        $query->getQuerySettings()->setRespectSysLanguage($arguments['respectSysLanguage']);

        if ($arguments['ignoreTimeRestrictions'] && $arguments['ignoreEnableFields']) {
            $query->getQuerySettings()->setIgnoreEnableFields(true);
            $query->getQuerySettings()->setEnableFieldsToBeIgnored(["starttime","endtime"]);
        }

        return $query;
    }

    /**
     * @param array $records
     * @param array $identifiers
     * @throws PropertyNotAccessibleException
     */
    protected static function sortRecordsByIdentifiersOrder(array &$records, array $identifiers): void
    {
        usort($records, function ($a, $b) use ($identifiers) {
            $aUid = ObjectAccess::getProperty($a, 'uid');
            $bUid = ObjectAccess::getProperty($b, 'uid');

            return array_search($aUid, $identifiers) - array_search($bUid, $identifiers);
        });
    }
}
