<?php declare(strict_types=1);

namespace Newland\Toubiz\Map\Neos\Provider\DefaultProviders;

use Neos\ContentRepository\Domain\Model\Node;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Domain\NodeType\NodeTypeConstraints;
use Neos\ContentRepository\Domain\NodeType\NodeTypeName;
use Neos\Eel\FlowQuery\FlowQuery;
use Neos\Flow\ObjectManagement\Exception\InvalidObjectException;
use Newland\Toubiz\Map\Neos\Provider\Contract\Marker;
use Newland\Toubiz\Map\Neos\Provider\Contract\MarkerProvider;
use Newland\Toubiz\Map\Neos\Provider\Contract\ProviderContext;
use Newland\Toubiz\Map\Neos\Provider\MapDataProviderService;
use Newland\Toubiz\Map\Neos\Provider\Pagination\Paginator;

abstract class SubnodeBasedMarkerProvider implements MarkerProvider
{
    public const FILTER_ITEM = 'Newland.Toubiz.Map.Neos:Map.FilterItem';
    public const CONTENT_COLLECTION = 'Neos.Neos:ContentCollection';

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

    /** @return Marker[] */
    abstract public function markersForNode(Node $node, int $offset, int  $limit): array;

    abstract public function numberOfMarkersForNode(Node $node): int;

    public function applies(Node $node): bool
    {
        return $node->getNodeType()->isOfType($this->nodeType);
    }

    public function getNumberOfMarkers(ProviderContext $context): int
    {
        $number = 0;
        foreach ($this->findMarkerNodes($context->mapNode()) as $node) {
            $number += $this->numberOfMarkersForNode($node);
        }
        return $number;
    }

    public function getMarkers(ProviderContext $context, int $offset, int $limit): array
    {
        if (empty($this->nodeType)) {
            throw new InvalidObjectException(sprintf(
                'The $nodeType property is not configured in subnode based marker provider `%s`',
                static::class
            ));
        }

        $paginator = new Paginator(10);
        foreach ($this->findMarkerNodes($context->mapNode()) as $node) {
            $paginator->addProvider(
                $this->numberOfMarkersForNode($node),
                function (int $offset, int $limit) use ($node) {
                    return $this->markersForNode($node, $offset, $limit);
                }
            );
        }

        return $paginator->getOffsetLimit($offset, $limit);
    }

    public function markerCacheKey(ProviderContext $context): ?string
    {
        return md5(static::class . $context->mapNode()->getContextPath());
    }


    private function findMarkerNodes(Node $node): \Generator
    {
        $query = (new FlowQuery([ $node ]))
            ->find(sprintf('[instanceof %s]', $this->nodeType));

        foreach ($query as $item) {
            if (!$item->isHidden()) {
                yield $item;
            }
        }
    }
}
