<?php

declare(strict_types=1);

namespace App\Framework\Pagination;

use App\Framework\Database\EntityManager;
use App\Framework\Database\QueryBuilder\SelectQueryBuilder;
use App\Framework\Pagination\ValueObjects\PaginationRequest;
use App\Framework\Pagination\ValueObjects\PaginationResponse;
use InvalidArgumentException;

/**
 * Main pagination service that manages different paginators
 */
final readonly class PaginationService
{
    public function __construct(
        private EntityManager $entityManager
    ) {
    }

    /**
     * Create paginator for array data
     *
     * @param array<mixed> $data
     */
    public function forArray(array $data): ArrayPaginator
    {
        return new ArrayPaginator($data);
    }

    /**
     * Create paginator for database entity
     */
    public function forEntity(string $entityClass): DatabasePaginator
    {
        return new DatabasePaginator(
            entityManager: $this->entityManager,
            entityClass: $entityClass
        );
    }

    /**
     * Create paginator for custom query
     */
    public function forQuery(string $entityClass, SelectQueryBuilder $queryBuilder): DatabasePaginator
    {
        return new DatabasePaginator(
            entityManager: $this->entityManager,
            entityClass: $entityClass,
            queryBuilder: $queryBuilder
        );
    }

    /**
     * Paginate data using appropriate paginator
     */
    public function paginate(mixed $source, PaginationRequest $request): PaginationResponse
    {
        $paginator = $this->resolvePaginator($source);

        if (! $paginator->supports($request)) {
            throw new InvalidArgumentException(
                'Paginator ' . get_class($paginator) . ' does not support the given request type'
            );
        }

        return $paginator->paginate($request);
    }

    /**
     * Resolve appropriate paginator for data source
     */
    private function resolvePaginator(mixed $source): Paginator
    {
        return match (true) {
            is_array($source) => new ArrayPaginator($source),
            $source instanceof SelectQueryBuilder => new DatabasePaginator(
                entityManager: $this->entityManager,
                entityClass: 'UnknownEntity', // This should be improved
                queryBuilder: $source
            ),
            is_string($source) && class_exists($source) => new DatabasePaginator(
                entityManager: $this->entityManager,
                entityClass: $source
            ),
            default => throw new InvalidArgumentException(
                'Cannot resolve paginator for source type: ' . get_debug_type($source)
            )
        };
    }

    /**
     * Create offset-based pagination request
     */
    public function offsetRequest(
        int $limit,
        int $offset = 0,
        ?string $sortField = null,
        string $direction = 'asc'
    ): PaginationRequest {
        return PaginationRequest::offset(
            limit: $limit,
            offset: $offset,
            sortField: $sortField,
            direction: $direction === 'desc' ?
                \App\Framework\Pagination\ValueObjects\Direction::DESC :
                \App\Framework\Pagination\ValueObjects\Direction::ASC
        );
    }

    /**
     * Create cursor-based pagination request
     */
    public function cursorRequest(
        int $limit,
        string $cursorValue,
        ?string $sortField = null,
        string $direction = 'asc'
    ): PaginationRequest {
        $directionEnum = $direction === 'desc' ?
            \App\Framework\Pagination\ValueObjects\Direction::DESC :
            \App\Framework\Pagination\ValueObjects\Direction::ASC;

        $cursor = \App\Framework\Pagination\ValueObjects\Cursor::fromEncoded($cursorValue, $directionEnum);

        return PaginationRequest::cursor(
            limit: $limit,
            cursor: $cursor,
            sortField: $sortField
        );
    }

    /**
     * Create first page request
     */
    public function firstPageRequest(
        int $limit,
        ?string $sortField = null,
        string $direction = 'asc'
    ): PaginationRequest {
        return PaginationRequest::first(
            limit: $limit,
            sortField: $sortField,
            direction: $direction === 'desc' ?
                \App\Framework\Pagination\ValueObjects\Direction::DESC :
                \App\Framework\Pagination\ValueObjects\Direction::ASC
        );
    }
}
