<?php
namespace SabaiApps\Framework\Paginator;

/**
 * AbstractPaginator class for managing pagination of elements.
 * Provides methods for calculating offsets, limits, and navigating pages.
 * Implements Iterator and Countable interfaces for easy traversal and counting.
 */
abstract class AbstractPaginator implements \Iterator, \Countable {

	protected $_perpage, $_limit, $_key = 0, $_currentPage = 1, $_elementOffset = 0, $_elementLimit;
	private $_elementCount;

	/**
	 * Constructor.
	 *
	 * @param int $perpage Number of items per page.
	 * @param int $limit Maximum number of items to paginate (optional).
	 */
	public function __construct( $perpage, $limit = 0 ) {
		$this->_perpage = intval( $perpage ) ? $perpage : 10;
		$this->_limit   = $limit;
	}

	/**
	 * Gets the number of items per page.
	 *
	 * @return int
	 */
	public function getPerPage() {
		return $this->_perpage;
	}

	/**
	 * Sets the current page number.
	 *
	 * @param int $page Page number.
	 * @return $this
	 */
	public function setCurrentPage( $page ) {
		if ( $page <= 1 // invalid page
			|| $this->getElementCount() <= ( $page - 1 ) * $this->_perpage // the page does not exist
		) {
			$this->_currentPage = 1;
		} else {
			$this->_currentPage = $page;
		}
		$this->_elementLimit = null; // reset to recalculate

		return $this;
	}

	/**
	 * Gets the current page number.
	 *
	 * @return int
	 */
	public function getCurrentPage() {
		return $this->_currentPage;
	}

	/**
	 * Calculates the offset and limit for the current page.
	 *
	 * @return int Limit of elements for the current page.
	 */
	private function _calculateElementOffsetAndLimit() {
		if ( isset( $this->_elementLimit ) ) {
			return $this->_elementLimit;
		}

		if ( ! $count = $this->getElementCount() ) {
			$this->_elementLimit = 0;
			return $this->_elementLimit;
		}

		$this->_elementOffset = ( $this->_currentPage - 1 ) * $this->_perpage;
		if ( $this->_elementOffset > $count ) {
			$this->_elementOffset = 0;
		}
		$remaining           = $count - $this->_elementOffset;
		$this->_elementLimit = $remaining < $this->_perpage ? $remaining : $this->_perpage;

		return $this->_elementLimit;
	}

	/**
	 * Gets the offset for the current page.
	 *
	 * @return int
	 */
	public function getElementOffset() {
		$this->_calculateElementOffsetAndLimit();

		return $this->_elementOffset;
	}

	/**
	 * Gets the limit for the current page.
	 *
	 * @return int
	 */
	public function getElementLimit() {
		$this->_calculateElementOffsetAndLimit();

		return $this->_elementLimit;
	}

	/**
	 * Gets the elements for the current page.
	 *
	 * @return \Traversable
	 */
	public function getElements() {
		if ( 0 === $this->_calculateElementOffsetAndLimit() ) {
			return $this->_getEmptyElements();
		}

		return $this->_getElements( $this->_elementLimit, $this->_elementOffset );
	}

	/**
	 * Gets an empty collection of elements.
	 *
	 * @return \ArrayObject
	 */
	protected function _getEmptyElements() {
		return new \ArrayObject( array() );
	}

	/**
	 * Gets the total count of elements.
	 *
	 * @return int
	 */
	public function getElementCount() {
		if ( ! isset( $this->_elementCount ) ) {
			$this->_elementCount = $this->_getElementCount();
			if ( $this->_limit
				&& $this->_elementCount > $this->_limit
			) {
				$this->_elementCount = $this->_limit;
			}
		}
		return $this->_elementCount;
	}

	#[\ReturnTypeWillChange]
	public function count() {
		return ceil( $this->getElementCount() / $this->_perpage );
	}

	#[\ReturnTypeWillChange]
	public function rewind() {
		$this->_key = 0;
	}

	#[\ReturnTypeWillChange]
	public function valid() {
		return $this->getElementCount() > $this->key() * $this->_perpage;
	}

	#[\ReturnTypeWillChange]
	public function next() {
		++$this->_key;
	}

	#[\ReturnTypeWillChange]
	public function current() {
		return $this->key() + 1;
	}

	#[\ReturnTypeWillChange]
	public function key() {
		return $this->_key;
	}

	/**
	 * Abstract method for fetching elements for the current page.
	 *
	 * @param int $limit Number of elements to fetch.
	 * @param int $offset Offset for fetching elements.
	 * @return \Traversable
	 */
	abstract protected function _getElements( $limit, $offset );

	/**
	 * Abstract method for getting the total count of elements.
	 *
	 * @return int
	 */
	abstract protected function _getElementCount();
}
