<?php

declare(strict_types=1);

namespace App\Framework\Core\Encoding;

/**
 * Base32 Alphabet Enum
 *
 * Defines different Base32 alphabets for various use cases:
 * - RFC3548: Standard Base32 alphabet for TOTP and general use
 * - CROCKFORD: Crockford's Base32 for ULIDs and human-readable IDs
 */
enum Base32Alphabet: string
{
    case RFC3548 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
    case CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';

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

    /**
     * Check if this alphabet uses padding
     */
    public function usesPadding(): bool
    {
        return match ($this) {
            self::RFC3548 => true,
            self::CROCKFORD => false
        };
    }

    /**
     * Get the character count (always 32 for Base32)
     */
    public function getCharacterCount(): int
    {
        return 32;
    }

    /**
     * Validate if a character exists in this alphabet
     */
    public function containsCharacter(string $char): bool
    {
        return str_contains($this->value, strtoupper($char));
    }

    /**
     * Get the index of a character in the alphabet
     */
    public function getCharacterIndex(string $char): int
    {
        $index = strpos($this->value, strtoupper($char));

        if ($index === false) {
            throw new \InvalidArgumentException("Character '{$char}' not found in {$this->name} alphabet");
        }

        return $index;
    }

    /**
     * Get character at specific index
     */
    public function getCharacterAt(int $index): string
    {
        if ($index < 0 || $index >= 32) {
            throw new \InvalidArgumentException("Index must be between 0 and 31, got {$index}");
        }

        return $this->value[$index];
    }

    /**
     * Validate an encoded string against this alphabet
     */
    public function isValidEncoded(string $encoded): bool
    {
        // Remove padding if this alphabet uses it
        if ($this->usesPadding()) {
            $encoded = rtrim($encoded, '=');
        }

        $encoded = strtoupper($encoded);

        // Check if all characters are valid
        for ($i = 0, $len = strlen($encoded); $i < $len; $i++) {
            if (! $this->containsCharacter($encoded[$i])) {
                return false;
            }
        }

        return true;
    }

    /**
     * Get recommended use cases for this alphabet
     * @return array<int, string>
     */
    public function getUseCases(): array
    {
        return match ($this) {
            self::RFC3548 => [
                'TOTP secrets',
                'General Base32 encoding',
                'Email verification codes',
                'API tokens',
            ],
            self::CROCKFORD => [
                'ULIDs',
                'Human-readable identifiers',
                'Short URLs',
                'Database primary keys',
            ]
        };
    }

    /**
     * Get description of this alphabet
     */
    public function getDescription(): string
    {
        return match ($this) {
            self::RFC3548 => 'RFC 3548 standard Base32 alphabet with padding',
            self::CROCKFORD => 'Crockford\'s Base32 alphabet without padding, optimized for human readability'
        };
    }

    /**
     * Generate a random string using this alphabet
     */
    public function generateRandom(int $length): string
    {
        if ($length < 1) {
            throw new \InvalidArgumentException('Length must be positive');
        }

        $result = '';
        for ($i = 0; $i < $length; $i++) {
            $result .= $this->value[random_int(0, 31)];
        }

        return $result;
    }
}
