<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Caching;

/**
 * Cache Context Value Object
 *
 * Represents contextual data that can affect cache keys.
 * Used for varyBy cache key generation.
 *
 * Examples:
 * - User context: userId, roles, permissions
 * - Locale context: language, timezone
 * - Feature flags: enabled features for A/B testing
 * - Request context: device type, user agent
 *
 * Framework Pattern: Immutable readonly Value Object
 */
final readonly class CacheContext
{
    /**
     * @param string|null $userId - Current user ID (null for guests)
     * @param string $locale - Current locale (e.g., "en", "de")
     * @param array<string> $roles - User roles
     * @param array<string, bool> $featureFlags - Enabled feature flags
     * @param array<string, mixed> $custom - Custom context data
     */
    public function __construct(
        public ?string $userId = null,
        public string $locale = 'en',
        public array $roles = [],
        public array $featureFlags = [],
        public array $custom = []
    ) {
    }

    /**
     * Create context for guest user
     */
    public static function guest(string $locale = 'en'): self
    {
        return new self(
            userId: null,
            locale: $locale
        );
    }

    /**
     * Create context for authenticated user
     */
    public static function forUser(
        string $userId,
        array $roles = [],
        string $locale = 'en'
    ): self {
        return new self(
            userId: $userId,
            locale: $locale,
            roles: $roles
        );
    }

    /**
     * Create context with feature flags
     */
    public function withFeatureFlags(array $featureFlags): self
    {
        return new self(
            userId: $this->userId,
            locale: $this->locale,
            roles: $this->roles,
            featureFlags: $featureFlags,
            custom: $this->custom
        );
    }

    /**
     * Create context with custom data
     */
    public function withCustom(string $key, mixed $value): self
    {
        return new self(
            userId: $this->userId,
            locale: $this->locale,
            roles: $this->roles,
            featureFlags: $this->featureFlags,
            custom: [...$this->custom, $key => $value]
        );
    }

    /**
     * Check if user is authenticated
     */
    public function isAuthenticated(): bool
    {
        return $this->userId !== null;
    }

    /**
     * Check if user has specific role
     */
    public function hasRole(string $role): bool
    {
        return in_array($role, $this->roles, true);
    }

    /**
     * Check if feature flag is enabled
     */
    public function hasFeature(string $feature): bool
    {
        return $this->featureFlags[$feature] ?? false;
    }

    /**
     * Get custom value
     */
    public function getCustom(string $key, mixed $default = null): mixed
    {
        return $this->custom[$key] ?? $default;
    }

    /**
     * Convert to array for serialization
     */
    public function toArray(): array
    {
        return [
            'user_id' => $this->userId,
            'locale' => $this->locale,
            'roles' => $this->roles,
            'feature_flags' => $this->featureFlags,
            'custom' => $this->custom,
        ];
    }

    /**
     * Generate cache key suffix from context
     *
     * Creates a deterministic string representation for cache keys.
     */
    public function toCacheKeySuffix(): string
    {
        $parts = [];

        if ($this->userId !== null) {
            $parts[] = "u:{$this->userId}";
        }

        if ($this->locale !== 'en') {
            $parts[] = "l:{$this->locale}";
        }

        if (! empty($this->roles)) {
            sort($this->roles);
            $parts[] = 'r:' . implode(',', $this->roles);
        }

        if (! empty($this->featureFlags)) {
            $enabledFeatures = array_keys(array_filter($this->featureFlags));
            sort($enabledFeatures);
            $parts[] = 'f:' . implode(',', $enabledFeatures);
        }

        if (! empty($this->custom)) {
            ksort($this->custom);
            foreach ($this->custom as $key => $value) {
                $parts[] = "{$key}:" . $this->serializeValue($value);
            }
        }

        return empty($parts) ? 'default' : implode(':', $parts);
    }

    /**
     * Serialize value for cache key
     */
    private function serializeValue(mixed $value): string
    {
        if (is_bool($value)) {
            return $value ? '1' : '0';
        }

        if (is_scalar($value)) {
            return (string) $value;
        }

        if (is_array($value)) {
            return md5(json_encode($value));
        }

        return md5(serialize($value));
    }
}
