<?php

namespace NoahVet\Reef\Iterator;

use NoahVet\Reef\Domain\Http\Header\ContentRangeHttpHeader;
use Psr\Http\Message\ResponseInterface;

/**
 * @template TValue of object
 * @template TParam of array<string, mixed>
 *
 * @implements ReefApiIteratorInterface<TValue, TParam>
 */
abstract class AbstractSimpleReefApiIterator implements ReefApiIteratorInterface
{
    protected int $i = 0;

    protected bool $isLastPage = false;

    protected bool $isValid = false;

    protected int $page = 0;

    /**
     * @var list<TValue>|null
     */
    protected ?array $pageItems = null;

    /**
     * @param TParam $params
     */
    public function __construct(
        protected array $params,
        protected readonly int $querySize = 50,
    ) {
    }

    /**
     * @param TParam&array{page: int, limit: int} $params
     */
    abstract public function apiCall(array $params): ResponseInterface;

    /**
     * @return list<TValue>
     */
    abstract public function parseResponse(ResponseInterface $response): array;

    /**
     * @return TValue
     */
    #[\Override]
    public function current(): mixed
    {
        if (null === $this->pageItems) {
            $this->pageItems = $this->loadPage($this->params, $this->page, $this->querySize);
        }

        return $this->pageItems[$this->i];
    }

    #[\Override]
    public function next(): void
    {
        ++$this->i;

        if (null === $this->pageItems) {
            throw new \LogicException('Cannot call next() before a first current() call');
        }

        if ($this->i >= \count($this->pageItems)) {
            ++$this->page;
            $this->i = 0;
            $this->pageItems = null;
            $this->isValid = !$this->isLastPage;

            // Load the next page if possible
            if ($this->isValid) {
                $this->current();
            }
        }
    }

    /**
     * @return int
     */
    #[\Override]
    public function key(): mixed
    {
        return ($this->page * $this->querySize) + $this->i;
    }

    #[\Override]
    public function valid(): bool
    {
        return $this->isValid;
    }

    #[\Override]
    public function rewind(): void
    {
        $this->page = 0;
        $this->i = 0;
        $this->pageItems = null;
        $this->isLastPage = false;

        $this->pageItems = $this->loadPage($this->params, $this->page, $this->querySize);
        $this->isValid = $this->i < \count($this->pageItems);
    }

    #[\Override]
    public function getParams(): array
    {
        return $this->params;
    }

    /**
     * @param TParam $params
     *
     * @return list<TValue>
     */
    public function loadPage(array $params, int $page, int $querySize): array
    {
        /** @var array{page: int, limit: int}&TParam $paramsWithPage */
        $paramsWithPage = \array_merge($params, ['page' => $page, 'limit' => $querySize]);

        $response = $this->apiCall($paramsWithPage);

        $items = $this->parseResponse($response);
        $this->isLastPage = $this->checkIfLastPage($response, $querySize, $items);

        return $items;
    }

    #[\Override]
    public function setParams(array $params): void
    {
        $this->params = $params;
        $this->rewind();
    }

    /**
     * @param list<TValue> $items
     *
     * @return bool true if the enumerator should stop, false if it should try another page
     */
    private function checkIfLastPage(ResponseInterface $response, int $querySize, array $items): bool
    {
        if (\count($items) < $querySize) {
            return true;
        }

        if (200 === $response->getStatusCode()) {
            return true;
        }

        if (206 === $response->getStatusCode()) {
            // Handling of content-range header
            $rawContentRange = $response->getHeader('Content-Range')[0] ?? null;
            if (null !== $rawContentRange && !empty($rawContentRange)) {
                $rangeInfo = ContentRangeHttpHeader::parse($rawContentRange);

                return $rangeInfo['size'] - 1 <= $rangeInfo['rangeEnd'];
            }
        }

        return false;
    }
}
