<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Caching;

use App\Framework\Cache\CacheKey;

/**
 * Cache Key Builder
 *
 * Builds intelligent cache keys for LiveComponents with varyBy support.
 *
 * Key Format: livecomponent:{component_name}:{base_key}:{vary_suffix}
 *
 * Examples:
 * - livecomponent:user-stats:total-count:u:123:l:en
 * - livecomponent:product-list:category-5:l:de:f:new-ui
 * - livecomponent:dashboard:widgets:global
 *
 * Framework Pattern: Readonly service class
 */
final readonly class CacheKeyBuilder
{
    /**
     * Build cache key with varyBy support
     *
     * @param string $componentName - Component name (e.g., "user-stats")
     * @param string $baseKey - Base cache key from component
     * @param VaryBy|null $varyBy - VaryBy specification
     * @param CacheContext|null $context - Current cache context
     * @return CacheKey - Complete cache key
     */
    public function build(
        string $componentName,
        string $baseKey,
        ?VaryBy $varyBy = null,
        ?CacheContext $context = null
    ): CacheKey {
        // Start with component prefix
        $parts = ['livecomponent', $componentName, $baseKey];

        // Add varyBy suffix if specified
        if ($varyBy && $context) {
            $varySuffix = $varyBy->apply($context);
            $parts[] = $varySuffix;
        } else {
            $parts[] = 'global';
        }

        $keyString = implode(':', $parts);

        return CacheKey::fromString($keyString);
    }

    /**
     * Build cache key from callable varyBy
     *
     * Allows dynamic varyBy logic per component.
     *
     * @param string $componentName - Component name
     * @param string $baseKey - Base cache key
     * @param callable $varyByCallable - Callable that returns VaryBy
     * @param CacheContext|null $context - Current cache context
     * @return CacheKey - Complete cache key
     */
    public function buildFromCallable(
        string $componentName,
        string $baseKey,
        callable $varyByCallable,
        ?CacheContext $context = null
    ): CacheKey {
        $varyBy = $varyByCallable($context);

        if (! $varyBy instanceof VaryBy) {
            throw new \InvalidArgumentException(
                'varyBy callable must return VaryBy instance'
            );
        }

        return $this->build($componentName, $baseKey, $varyBy, $context);
    }

    /**
     * Build cache key with array-based varyBy
     *
     * Convenience method for simple varyBy arrays.
     *
     * @param string $componentName - Component name
     * @param string $baseKey - Base cache key
     * @param array $varyByArray - Array like ['userId', 'locale']
     * @param CacheContext|null $context - Current cache context
     * @return CacheKey - Complete cache key
     */
    public function buildFromArray(
        string $componentName,
        string $baseKey,
        array $varyByArray,
        ?CacheContext $context = null
    ): CacheKey {
        $varyBy = $this->varyByFromArray($varyByArray);

        return $this->build($componentName, $baseKey, $varyBy, $context);
    }

    /**
     * Convert array to VaryBy
     *
     * Supports: 'userId', 'locale', 'roles', ['featureFlags' => [...]], ['custom' => [...]]
     */
    private function varyByFromArray(array $array): VaryBy
    {
        $userId = in_array('userId', $array, true);
        $locale = in_array('locale', $array, true);
        $roles = in_array('roles', $array, true);

        $featureFlags = [];
        $custom = [];

        foreach ($array as $key => $value) {
            if ($key === 'featureFlags' && is_array($value)) {
                $featureFlags = $value;
            } elseif ($key === 'custom' && is_array($value)) {
                $custom = $value;
            }
        }

        return new VaryBy(
            userId: $userId,
            locale: $locale,
            roles: $roles,
            featureFlags: $featureFlags,
            custom: $custom
        );
    }

    /**
     * Parse cache key to extract components
     *
     * Useful for debugging and introspection.
     *
     * @param CacheKey $cacheKey - Cache key to parse
     * @return array{component: string, base_key: string, vary_suffix: string}|null
     */
    public function parse(CacheKey $cacheKey): ?array
    {
        $parts = explode(':', $cacheKey->toString());

        if (count($parts) < 4 || $parts[0] !== 'livecomponent') {
            return null;
        }

        return [
            'component' => $parts[1],
            'base_key' => $parts[2],
            'vary_suffix' => implode(':', array_slice($parts, 3)),
        ];
    }

    /**
     * Check if cache key belongs to specific component
     */
    public function isComponentKey(CacheKey $cacheKey, string $componentName): bool
    {
        $parsed = $this->parse($cacheKey);

        return $parsed && $parsed['component'] === $componentName;
    }
}
