<?php

declare(strict_types=1);

namespace App\Framework\NanoId;

use InvalidArgumentException;

/**
 * NanoId Value Object
 *
 * A URL-safe, unique string ID generator.
 * Default alphabet: A-Za-z0-9_-
 * Default size: 21 characters
 */
final readonly class NanoId
{
    public const int DEFAULT_SIZE = 21;
    public const string DEFAULT_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-';

    // URL-safe alphabet without lookalikes
    public const string SAFE_ALPHABET = '23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnpqrstwxyz';

    // Numbers only
    public const string NUMBERS = '0123456789';

    // Lowercase alphanumeric
    public const string LOWERCASE_ALPHANUMERIC = '0123456789abcdefghijklmnopqrstuvwxyz';

    // Uppercase alphanumeric
    public const string UPPERCASE_ALPHANUMERIC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';

    private string $value;

    public function __construct(string $value)
    {
        if (empty($value)) {
            throw new InvalidArgumentException('NanoId cannot be empty');
        }

        if (strlen($value) > 255) {
            throw new InvalidArgumentException('NanoId cannot exceed 255 characters');
        }

        $this->value = $value;
    }

    /**
     * Create from an existing string
     */
    public static function fromString(string $value): self
    {
        return new self($value);
    }

    /**
     * Validate if a string matches a specific alphabet
     */
    public function matchesAlphabet(string $alphabet): bool
    {
        $pattern = '/^[' . preg_quote($alphabet, '/') . ']+$/';

        return preg_match($pattern, $this->value) === 1;
    }

    /**
     * Check if this is a valid default NanoId
     */
    public function isDefault(): bool
    {
        return $this->matchesAlphabet(self::DEFAULT_ALPHABET);
    }

    /**
     * Check if this is a safe NanoId (no lookalikes)
     */
    public function isSafe(): bool
    {
        return $this->matchesAlphabet(self::SAFE_ALPHABET);
    }

    /**
     * Check if this is a numeric NanoId
     */
    public function isNumeric(): bool
    {
        return ctype_digit($this->value);
    }

    /**
     * Get the length of the NanoId
     */
    public function getLength(): int
    {
        return strlen($this->value);
    }

    /**
     * Get the string value
     */
    public function toString(): string
    {
        return $this->value;
    }

    /**
     * Get the string value
     */
    public function getValue(): string
    {
        return $this->value;
    }

    /**
     * Magic method for string conversion
     */
    public function __toString(): string
    {
        return $this->value;
    }

    /**
     * Check equality with another NanoId
     */
    public function equals(self $other): bool
    {
        return $this->value === $other->value;
    }

    /**
     * Create a prefixed NanoId
     */
    public function withPrefix(string $prefix): self
    {
        if (empty($prefix)) {
            throw new InvalidArgumentException('Prefix cannot be empty');
        }

        return new self($prefix . $this->value);
    }

    /**
     * Create a suffixed NanoId
     */
    public function withSuffix(string $suffix): self
    {
        if (empty($suffix)) {
            throw new InvalidArgumentException('Suffix cannot be empty');
        }

        return new self($this->value . $suffix);
    }

    /**
     * Get a truncated version of the NanoId
     */
    public function truncate(int $length): self
    {
        if ($length <= 0) {
            throw new InvalidArgumentException('Length must be positive');
        }

        if ($length >= strlen($this->value)) {
            return $this;
        }

        return new self(substr($this->value, 0, $length));
    }
}
