<?php

/**
 * Paginated Result Class
 *
 * Represents a paginated collection of results with metadata and
 * convenience methods for pagination handling.
 *
 * @package     WPUltimateSecurity\UltimateSecurity\Infrastructure\Persistence
 * @since       2.0.0
 */

declare(strict_types=1);

namespace WPUltimateSecurity\UltimateSecurity\Infrastructure\Persistence;

/**
 * Immutable paginated result container.
 *
 * Provides:
 * - Pagination metadata (total, perPage, currentPage)
 * - Calculated values (totalPages, hasMorePages)
 * - Item access and iteration
 * - Array serialization for API responses
 *
 * @since 2.0.0
 *
 * @implements \Countable
 * @implements \IteratorAggregate<int, mixed>
 */
class PaginatedResult implements \Countable, \IteratorAggregate
{
    /**
     * Constructor.
     *
     * @since 2.0.0
     *
     * @param array<int, mixed> $items       The items for the current page.
     * @param int               $total       Total number of items across all pages.
     * @param int               $perPage     Number of items per page.
     * @param int               $currentPage Current page number (1-indexed).
     */
    public function __construct(
        public readonly array $items,
        public readonly int $total,
        public readonly int $perPage,
        public readonly int $currentPage
    ) {
    }

    /**
     * Calculate the total number of pages.
     *
     * @since 2.0.0
     *
     * @return int Total number of pages.
     */
    public function totalPages(): int
    {
        if ($this->perPage <= 0) {
            return 0;
        }

        return (int) ceil($this->total / $this->perPage);
    }

    /**
     * Check if there are more pages after the current one.
     *
     * @since 2.0.0
     *
     * @return bool True if more pages exist.
     */
    public function hasMorePages(): bool
    {
        return $this->currentPage < $this->totalPages();
    }

    /**
     * Check if the result set is empty.
     *
     * @since 2.0.0
     *
     * @return bool True if no items on current page.
     */
    public function isEmpty(): bool
    {
        return count($this->items) === 0;
    }

    /**
     * Get the number of items on the current page.
     *
     * @since 2.0.0
     *
     * @return int Number of items.
     */
    public function count(): int
    {
        return count($this->items);
    }

    /**
     * Get the 1-based index of the first item on the current page.
     *
     * @since 2.0.0
     *
     * @return int First item index (1-based), or 0 if empty.
     */
    public function firstItem(): int
    {
        if ($this->isEmpty()) {
            return 0;
        }

        return (($this->currentPage - 1) * $this->perPage) + 1;
    }

    /**
     * Get the 1-based index of the last item on the current page.
     *
     * @since 2.0.0
     *
     * @return int Last item index (1-based), or 0 if empty.
     */
    public function lastItem(): int
    {
        if ($this->isEmpty()) {
            return 0;
        }

        return $this->firstItem() + $this->count() - 1;
    }

    /**
     * Get the items for the current page.
     *
     * Alias for accessing the items property directly.
     *
     * @since 2.0.0
     *
     * @return array<int, mixed> The items array.
     */
    public function getItems(): array
    {
        return $this->items;
    }

    /**
     * Get an iterator for the items.
     *
     * Allows foreach iteration over the paginated result.
     *
     * @since 2.0.0
     *
     * @return \ArrayIterator<int, mixed>
     */
    public function getIterator(): \ArrayIterator
    {
        return new \ArrayIterator($this->items);
    }

    /**
     * Convert the paginated result to an array.
     *
     * Includes all data and calculated fields suitable for API responses.
     *
     * @since 2.0.0
     *
     * @return array{
     *     items: array<int, mixed>,
     *     total: int,
     *     per_page: int,
     *     current_page: int,
     *     total_pages: int,
     *     has_more_pages: bool,
     *     first_item: int,
     *     last_item: int,
     *     is_empty: bool
     * }
     */
    public function toArray(): array
    {
        return [
            'items'          => $this->items,
            'total'          => $this->total,
            'per_page'       => $this->perPage,
            'current_page'   => $this->currentPage,
            'total_pages'    => $this->totalPages(),
            'has_more_pages' => $this->hasMorePages(),
            'first_item'     => $this->firstItem(),
            'last_item'      => $this->lastItem(),
            'is_empty'       => $this->isEmpty(),
        ];
    }

    /**
     * Create a PaginatedResult from an array.
     *
     * Factory method for creating instances from array data,
     * such as from QueryBuilder::paginate() results.
     *
     * @since 2.0.0
     *
     * @param array{items: array<int, mixed>, total: int, per_page: int, current_page: int} $data Pagination data.
     *
     * @return self
     */
    public static function fromArray(array $data): self
    {
        return new self(
            items: $data['items'] ?? [],
            total: $data['total'] ?? 0,
            perPage: $data['per_page'] ?? 15,
            currentPage: $data['current_page'] ?? 1
        );
    }
}
