<?php

declare(strict_types=1);

namespace App\Framework\Pagination\ValueObjects;

use App\Framework\Serializer\Exception\DeserializeException;
use App\Framework\Serializer\Exception\SerializeException;
use App\Framework\Serializer\Json\JsonSerializer;
use InvalidArgumentException;

/**
 * Cursor for cursor-based pagination
 */
final readonly class Cursor
{
    public function __construct(
        public string $value,
        public Direction $direction = Direction::ASC
    ) {
        if (empty($this->value)) {
            throw new InvalidArgumentException('Cursor value cannot be empty');
        }
    }

    /**
     * Create cursor from base64 encoded value
     */
    public static function fromEncoded(string $encodedValue, Direction $direction = Direction::ASC): self
    {
        $decoded = base64_decode($encodedValue, true);
        if ($decoded === false) {
            throw new InvalidArgumentException('Invalid cursor encoding');
        }

        return new self($decoded, $direction);
    }

    /**
     * Create cursor from field value
     */
    public static function fromField(string $field, mixed $value, Direction $direction = Direction::ASC): self
    {
        $serializer = new JsonSerializer();

        try {
            $cursorValue = $serializer->serialize([$field => $value]);
        } catch (SerializeException $e) {
            throw new InvalidArgumentException('Cannot encode cursor value: ' . $e->getMessage(), 0, $e);
        }

        return new self($cursorValue, $direction);
    }

    /**
     * Get base64 encoded cursor value
     */
    public function toEncoded(): string
    {
        return base64_encode($this->value);
    }

    /**
     * Parse cursor value as array
     */
    public function parseValue(): array
    {
        $serializer = new JsonSerializer();

        try {
            $parsed = $serializer->deserialize($this->value);
        } catch (DeserializeException $e) {
            throw new InvalidArgumentException('Cursor value is not valid JSON: ' . $e->getMessage(), 0, $e);
        }

        if (! is_array($parsed)) {
            throw new InvalidArgumentException('Cursor value is not an array');
        }

        return $parsed;
    }

    /**
     * Get field value from cursor
     */
    public function getFieldValue(string $field): mixed
    {
        $parsed = $this->parseValue();

        return $parsed[$field] ?? null;
    }

    /**
     * Check if cursor is for ascending direction
     */
    public function isAscending(): bool
    {
        return $this->direction->isAscending();
    }

    /**
     * Check if cursor is for descending direction
     */
    public function isDescending(): bool
    {
        return $this->direction->isDescending();
    }

    /**
     * Create new cursor with different direction
     */
    public function withDirection(Direction $direction): self
    {
        return new self($this->value, $direction);
    }

    public function toString(): string
    {
        return $this->toEncoded();
    }

    public function __toString(): string
    {
        return $this->toString();
    }
}
