<?php

declare(strict_types=1);

namespace App\Framework\Pagination;

use App\Framework\Pagination\ValueObjects\Cursor;
use App\Framework\Pagination\ValueObjects\Direction;
use App\Framework\Pagination\ValueObjects\PaginationRequest;
use App\Framework\Pagination\ValueObjects\PaginationResponse;

/**
 * Array-based paginator for in-memory pagination
 */
final readonly class ArrayPaginator implements Paginator
{
    /**
     * @param array<mixed> $data
     */
    public function __construct(
        private array $data
    ) {
    }

    /**
     * {@inheritdoc}
     */
    public function paginate(PaginationRequest $request): PaginationResponse
    {
        if ($request->isCursorBased()) {
            return $this->paginateWithCursor($request);
        }

        return $this->paginateWithOffset($request);
    }

    /**
     * {@inheritdoc}
     */
    public function supports(PaginationRequest $request): bool
    {
        return true; // ArrayPaginator supports both offset and cursor pagination
    }

    /**
     * Paginate with offset-based pagination
     */
    private function paginateWithOffset(PaginationRequest $request): PaginationResponse
    {
        $sortedData = $this->sortData($this->data, $request->sortField, $request->direction);
        $totalCount = count($sortedData);

        $slice = array_slice($sortedData, $request->offset, $request->limit);

        return PaginationResponse::offset(
            data: $slice,
            request: $request,
            totalCount: $totalCount
        );
    }

    /**
     * Paginate with cursor-based pagination
     */
    private function paginateWithCursor(PaginationRequest $request): PaginationResponse
    {
        $sortedData = $this->sortData($this->data, $request->sortField, $request->direction);

        if ($request->cursor === null) {
            // First page
            $slice = array_slice($sortedData, 0, $request->limit);
            $nextCursor = $this->createNextCursor($slice, $request);

            return PaginationResponse::cursor(
                data: $slice,
                request: $request,
                nextCursor: $nextCursor
            );
        }

        // Find cursor position
        $cursorPosition = $this->findCursorPosition($sortedData, $request->cursor, $request->sortField);

        if ($cursorPosition === -1) {
            // Cursor not found, return empty
            return PaginationResponse::cursor(
                data: [],
                request: $request
            );
        }

        // Get slice based on direction
        if ($request->direction->isAscending()) {
            $slice = array_slice($sortedData, $cursorPosition + 1, $request->limit);
        } else {
            $startPosition = max(0, $cursorPosition - $request->limit);
            $slice = array_slice($sortedData, $startPosition, $cursorPosition - $startPosition);
        }

        $nextCursor = $this->createNextCursor($slice, $request);
        $previousCursor = $this->createPreviousCursor($slice, $request);

        return PaginationResponse::cursor(
            data: $slice,
            request: $request,
            nextCursor: $nextCursor,
            previousCursor: $previousCursor
        );
    }

    /**
     * Sort data by field and direction
     *
     * @param array<mixed> $data
     * @return array<mixed>
     */
    private function sortData(array $data, ?string $sortField, Direction $direction): array
    {
        if ($sortField === null) {
            return $data;
        }

        $sorted = $data;
        usort($sorted, function ($a, $b) use ($sortField, $direction) {
            $valueA = $this->getFieldValue($a, $sortField);
            $valueB = $this->getFieldValue($b, $sortField);

            $comparison = $valueA <=> $valueB;

            return $direction->isAscending() ? $comparison : -$comparison;
        });

        return $sorted;
    }

    /**
     * Get field value from item
     */
    private function getFieldValue(mixed $item, string $field): mixed
    {
        if (is_array($item)) {
            return $item[$field] ?? null;
        }

        if (is_object($item)) {
            return $item->$field ?? null;
        }

        return $item;
    }

    /**
     * Find cursor position in data
     *
     * @param array<mixed> $data
     */
    private function findCursorPosition(array $data, Cursor $cursor, ?string $sortField): int
    {
        if ($sortField === null) {
            return -1;
        }

        $cursorValue = $cursor->getFieldValue($sortField);

        foreach ($data as $index => $item) {
            $itemValue = $this->getFieldValue($item, $sortField);
            if ($itemValue === $cursorValue) {
                return $index;
            }
        }

        return -1;
    }

    /**
     * Create next cursor from slice
     *
     * @param array<mixed> $slice
     */
    private function createNextCursor(array $slice, PaginationRequest $request): ?Cursor
    {
        if (empty($slice) || $request->sortField === null) {
            return null;
        }

        $lastItem = end($slice);
        $lastValue = $this->getFieldValue($lastItem, $request->sortField);

        if ($lastValue === null) {
            return null;
        }

        return Cursor::fromField($request->sortField, $lastValue, $request->direction);
    }

    /**
     * Create previous cursor from slice
     *
     * @param array<mixed> $slice
     */
    private function createPreviousCursor(array $slice, PaginationRequest $request): ?Cursor
    {
        if (empty($slice) || $request->sortField === null) {
            return null;
        }

        $firstItem = reset($slice);
        $firstValue = $this->getFieldValue($firstItem, $request->sortField);

        if ($firstValue === null) {
            return null;
        }

        $oppositeDirection = $request->direction->opposite();

        return Cursor::fromField($request->sortField, $firstValue, $oppositeDirection);
    }
}
