<?php

declare(strict_types=1);

namespace App\Framework\Pagination\ValueObjects;

use App\Framework\Serializer\Json\JsonSerializer;

/**
 * Pagination response with data and metadata
 */
final readonly class PaginationResponse
{
    /**
     * @param array<mixed> $data
     */
    public function __construct(
        public array $data,
        public PaginationMeta $meta
    ) {
    }

    /**
     * Create response for offset-based pagination
     *
     * @param array<mixed> $data
     */
    public static function offset(
        array $data,
        PaginationRequest $request,
        int $totalCount
    ): self {
        $meta = PaginationMeta::offset(
            request: $request,
            totalCount: $totalCount,
            resultCount: count($data)
        );

        return new self($data, $meta);
    }

    /**
     * Create response for cursor-based pagination
     *
     * @param array<mixed> $data
     */
    public static function cursor(
        array $data,
        PaginationRequest $request,
        ?Cursor $nextCursor = null,
        ?Cursor $previousCursor = null
    ): self {
        $meta = PaginationMeta::cursor(
            request: $request,
            resultCount: count($data),
            nextCursor: $nextCursor,
            previousCursor: $previousCursor
        );

        return new self($data, $meta);
    }

    /**
     * Check if there are more results
     */
    public function hasMore(): bool
    {
        return $this->meta->hasMore();
    }

    /**
     * Check if there are previous results
     */
    public function hasPrevious(): bool
    {
        return $this->meta->hasPrevious();
    }

    /**
     * Get data count
     */
    public function count(): int
    {
        return count($this->data);
    }

    /**
     * Check if response is empty
     */
    public function isEmpty(): bool
    {
        return empty($this->data);
    }

    /**
     * Convert to array for JSON serialization
     */
    public function toArray(): array
    {
        return [
            'data' => $this->data,
            'meta' => $this->meta->toArray(),
        ];
    }

    /**
     * Convert to JSON string
     */
    public function toJson(): string
    {
        $serializer = new JsonSerializer();

        return $serializer->serialize($this->toArray());
    }

    /**
     * Get first item from data
     */
    public function first(): mixed
    {
        return $this->data[0] ?? null;
    }

    /**
     * Get last item from data
     */
    public function last(): mixed
    {
        if (empty($this->data)) {
            return null;
        }

        return $this->data[array_key_last($this->data)];
    }

    /**
     * Map data through a callback
     *
     * @param callable(mixed): mixed $callback
     */
    public function map(callable $callback): self
    {
        $mappedData = array_map($callback, $this->data);

        return new self($mappedData, $this->meta);
    }

    /**
     * Filter data through a callback
     *
     * @param callable(mixed): bool $callback
     */
    public function filter(callable $callback): self
    {
        $filteredData = array_values(array_filter($this->data, $callback));

        // Update meta with new result count
        $newMeta = $this->meta->withResultCount(count($filteredData));

        return new self($filteredData, $newMeta);
    }
}
