<?php

use KupShop\KupShopBundle\Pager\PagerExtraItem;

class Pager implements ArrayAccess
{
    public const INFINITE_PAGE_COUNT = 999999;

    protected $pageSideCount = 1;
    protected $pageInsideCount = 3;

    public $count;
    public $number;

    public $total;
    public ?int $totalWithExtraItems = null;
    public $onPage;

    protected $url;
    protected $params = [];

    protected int $extraItemsOnFirstPage = 0;
    /** @var PagerExtraItem[] */
    protected array $extraItemsOnEachPage = [];

    public function __construct($pageCount = null, $page = null, $objectCount = null)
    {
        if (!is_null($pageCount)) {
            $this->count = max(1, $pageCount);
        }

        if (!is_null($page)) {
            $this->number = $page;
        }
    }

    public function setUrl($url)
    {
        $parts = parse_url($url);

        $this->url = $parts['path'];

        if (!empty($parts['query'])) {
            parse_str($parts['query'], $this->params);
        }
    }

    /*
     * set total number of pages
     * $total = total number of records
     * $this->count = total number of pages
     * */
    public function setTotal($total, $onPage = null)
    {
        // total uz muze mit nejakou hodnotu, napr. pocet banneru v ProductListu (total number of records = bannery + produkty)
        $this->total += $total;

        if (is_null($this->count)) {
            $extraItems = array_sum($this->getExtraItems());
            $extraItems += $this->getExtraItemsOnNextPages(
                ceil(($this->total + $extraItems) / $this->getOnPage())
            );

            $this->totalWithExtraItems = $this->total + $extraItems;
            $this->count = ceil($this->totalWithExtraItems / $this->getOnPage());
        }

        if ($onPage) {
            $this->onPage = $onPage;
        }
    }

    /**
     * @param int $onPage
     *
     * @return $this
     */
    /*
     * set number of records on the page
     * */
    public function setOnPage($onPage)
    {
        $this->onPage = $onPage;

        return $this;
    }

    /**
     * @param int $number
     *
     * @return $this
     */
    public function setPageNumber($number)
    {
        $this->number = max(1, $number);

        return $this;
    }

    /**
     * @param int $pageSideCount
     *
     * @return $this
     */
    public function setPageSide($pageSideCount)
    {
        $this->pageSideCount = $pageSideCount;

        return $this;
    }

    /**
     * @param int $pageInsideCount
     *
     * @return $this
     */
    public function setPageInside($pageInsideCount)
    {
        $this->pageInsideCount = $pageInsideCount;

        return $this;
    }

    public function getUrl($values)
    {
        $params = array_merge($this->params, $values);

        if (getVal('page', $params, 0) == 1) {
            unset($params['page']);
        }

        $query = http_build_query($params);

        return $query ? '?'.$query : '.';
    }

    public function __get($name)
    {
        if (method_exists($this, $name)) {
            return $this->$name();
        }
    }

    public function first()
    {
        return $this->insidePagesFrom() > $this->pageSideCount;
    }

    public function firstDots()
    {
        return $this->insidePagesFrom() > $this->pageSideCount + 1;
    }

    public function last()
    {
        return $this->insidePagesTo() < ($this->count - $this->pageSideCount + 1);
    }

    public function lastDots()
    {
        return $this->insidePagesTo() < $this->count - $this->pageSideCount;
    }

    public function insidePagesFrom()
    {
        return max(1, $this->number - $this->pageInsideCount);
    }

    public function insidePagesTo()
    {
        return min($this->count, $this->number + $this->pageInsideCount);
    }

    public function to()
    {
        return min($this->number * $this->getOnPage() - 1, $this->total ?: self::INFINITE_PAGE_COUNT);
    }

    public function from()
    {
        return ($this->number - 1) * $this->getOnPage();
    }

    public function isInfinite(): bool
    {
        return $this->count === self::INFINITE_PAGE_COUNT;
    }

    public function getSpec()
    {
        return function (Query\QueryBuilder $qb) {
            [$extraItemsOnThisPage, $extraItemsOnPreviousPages] = $this->getExtraItems();

            $onPage = $this->getOnPage() - $extraItemsOnThisPage;
            $from = max(0, $this->from() - $extraItemsOnPreviousPages);

            $qb->setMaxResults($onPage)
                ->setFirstResult($from);

            return null;
        };
    }

    public function addExtraItems(PagerExtraItem $onEachPage): self
    {
        $this->extraItemsOnEachPage[] = $onEachPage;

        return $this;
    }

    /**
     * Get the extra items for the current and previous pages.
     *
     * @return array{onThisPage: int, onPreviousPages: int}
     */
    public function getExtraItems(): array
    {
        $extraItemsOnPreviousPages = 0;
        $extraItemsOnThisPage = 0;

        foreach ($this->extraItemsOnEachPage as $extraItem) {
            $extraItemsOnPreviousPages += min($extraItem->countOnPage * ($this->number - 1), $extraItem->getMax());
            $extraItemsOnThisPage += min($extraItem->countOnPage, max($extraItem->getMax() - $extraItemsOnPreviousPages, 0));
        }

        return [$extraItemsOnThisPage, $extraItemsOnPreviousPages];
    }

    /**
     * Get the extra items for the next pages when total pages count is already known.
     */
    private function getExtraItemsOnNextPages(int $totalPages): int
    {
        $extraItemsOnNextPages = 0;

        foreach ($this->extraItemsOnEachPage as $extraItem) {
            $extraItemsOnNextPages += min(($totalPages - $this->number) * $extraItem->countOnPage, max($extraItem->getMax() - ($extraItem->countOnPage * $this->number), 0));
        }

        return $extraItemsOnNextPages;
    }

    /**
     * Implements ArrayAccess interface.
     */
    public function offsetSet($offset, $value): void
    {
        $this->{$offset} = $value;
    }

    public function offsetExists($offset): bool
    {
        return isset($this->{$offset});
    }

    public function offsetUnset($offset): void
    {
        unset($this->{$offset});
    }

    public function offsetGet($offset): mixed
    {
        if (method_exists($this, $offset)) {
            return $this->$offset();
        }

        return isset($this->{$offset}) ? $this->{$offset} : null;
    }

    public function getOnPage()
    {
        if (empty($this->onPage)) {
            return 30;
        }

        return $this->onPage;
    }
}
