<?php

declare(strict_types=1);

namespace App\Framework\Cache;

use InvalidArgumentException;

/**
 * Cache prefix identifier for prefix-based operations
 * Allows operations on all cache items with specific prefix
 */
final readonly class CachePrefix implements CacheIdentifier
{
    private const int MAX_PREFIX_LENGTH = 100;
    private const string PREFIX_MARKER = 'prefix:';

    private function __construct(
        private string $value
    ) {
        $this->validate($value);
    }

    /**
     * Create cache prefix from string
     */
    public static function fromString(string $prefix): self
    {
        return new self($prefix);
    }

    /**
     * Create prefix for general cache items
     */
    public static function general(): self
    {
        return new self('cache:');
    }

    /**
     * Create prefix for query cache items
     */
    public static function query(): self
    {
        return new self('query_cache:');
    }

    /**
     * Create prefix for session items
     */
    public static function session(): self
    {
        return new self('session:');
    }

    /**
     * Create prefix for specific namespace
     */
    public static function forNamespace(string $namespace): self
    {
        return new self($namespace . ':');
    }

    /**
     * Create prefix for user-related items
     */
    public static function forUser(string|int $userId): self
    {
        return new self("user:{$userId}:");
    }

    /**
     * Create prefix for temporary items
     */
    public static function forTemporary(): self
    {
        return new self('temp:');
    }

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

    /**
     * Magic method to allow automatic string casting
     */
    public function __toString(): string
    {
        return $this->value;
    }

    /**
     * Get the type of cache identifier
     */
    public function getType(): CacheIdentifierType
    {
        return CacheIdentifierType::PREFIX;
    }

    /**
     * Check if this prefix equals another identifier
     */
    public function equals(CacheIdentifier $other): bool
    {
        return $other instanceof self && $this->value === $other->value;
    }

    /**
     * Check if this prefix matches a cache key
     */
    public function matchesKey(CacheKey $key): bool
    {
        return str_starts_with($key->toString(), $this->value);
    }

    /**
     * Get a normalized string for internal cache operations
     */
    public function getNormalizedString(): string
    {
        return self::PREFIX_MARKER . $this->value;
    }

    /**
     * Create a cache key with this prefix
     */
    public function createKey(string $suffix): CacheKey
    {
        return CacheKey::fromString($this->value . $suffix);
    }

    /**
     * Remove this prefix from a cache key string
     */
    public function removeFromKey(string $key): string
    {
        if (str_starts_with($key, $this->value)) {
            return substr($key, strlen($this->value));
        }

        return $key;
    }

    /**
     * Check if prefix ends with separator
     */
    public function hasTrailingSeparator(): bool
    {
        return str_ends_with($this->value, ':');
    }

    /**
     * Ensure prefix has trailing separator
     */
    public function withTrailingSeparator(): self
    {
        if ($this->hasTrailingSeparator()) {
            return $this;
        }

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

    /**
     * Validate the prefix
     */
    private function validate(string $prefix): void
    {
        if (empty($prefix)) {
            throw new InvalidArgumentException('Cache prefix cannot be empty');
        }

        if (strlen($prefix) > self::MAX_PREFIX_LENGTH) {
            throw new InvalidArgumentException(sprintf(
                'Cache prefix length exceeds maximum of %d characters (got %d)',
                self::MAX_PREFIX_LENGTH,
                strlen($prefix)
            ));
        }

        // Check for invalid characters
        if (preg_match('/[\s\n\r\t\0\x0B*?]/', $prefix)) {
            throw new InvalidArgumentException('Cache prefix contains invalid characters');
        }
    }
}
