<?php

declare(strict_types=1);

namespace App\Framework\Cache;

use App\Framework\Core\ValueObjects\Duration;

/**
 * Tagged cache utility for easy cache tagging operations
 *
 * Provides a fluent API for working with cache tags on top of SmartCache.
 * This is a convenience wrapper that makes tag operations more intuitive.
 */
final readonly class TaggedCache
{
    public function __construct(
        private SmartCache $cache,
        private array      $tags = []
    ) {
    }

    /**
     * Create a new TaggedCache instance with additional tags
     */
    public function tags(string|CacheTag ...$tags): self
    {
        $cacheTags = array_map(fn ($tag) => $tag instanceof CacheTag ? $tag : CacheTag::from($tag), $tags);
        $allTags = array_merge($this->tags, $cacheTags);

        return new self($this->cache, $allTags);
    }

    /**
     * Store a value with the current tags
     */
    public function put(string|CacheKey $key, mixed $value, ?Duration $ttl = null): bool
    {
        $cacheKey = $key instanceof CacheKey ? $key : CacheKey::fromString($key);
        $item = CacheItem::forSet($cacheKey, $value, $ttl);

        // Store the item
        $success = $this->cache->set($item);

        // Tag the item if successful and we have tags
        if ($success && ! empty($this->tags)) {
            $this->cache->tag($cacheKey, ...$this->tags);
        }

        return $success;
    }

    /**
     * Get a value (no automatic tagging for retrieval)
     */
    public function get(string|CacheKey $key): CacheItem
    {
        $cacheKey = $key instanceof CacheKey ? $key : CacheKey::fromString($key);
        $result = $this->cache->get($cacheKey);

        return $result->getItem($cacheKey);
    }

    /**
     * Remember a value with the current tags
     */
    public function remember(string|CacheKey $key, callable $callback, ?Duration $ttl = null): CacheItem
    {
        $cacheKey = $key instanceof CacheKey ? $key : CacheKey::fromString($key);
        $item = $this->get($cacheKey);

        if ($item->isHit) {
            return $item;
        }

        // Compute value and store with tags
        $value = $callback();
        $this->put($cacheKey, $value, $ttl);

        return CacheItem::hit($cacheKey, $value);
    }

    /**
     * Flush all items with any of the current tags
     */
    public function flush(): bool
    {
        if (empty($this->tags)) {
            return true;
        }

        $success = true;
        foreach ($this->tags as $tag) {
            $success &= $this->cache->forget($tag);
        }

        return $success;
    }

    /**
     * Get all items tagged with any of the current tags
     */
    public function all(): CacheResult
    {
        if (empty($this->tags)) {
            return CacheResult::empty();
        }

        return $this->cache->get(...$this->tags);
    }

    /**
     * Check if items with the current tags exist
     */
    public function exists(): bool
    {
        if (empty($this->tags)) {
            return false;
        }

        $results = $this->cache->has(...$this->tags);

        // Return true if any tagged item exists
        return in_array(true, $results, true);
    }

    /**
     * Get statistics for the current tags
     */
    public function getTagStats(): array
    {
        $tagIndex = $this->cache->getTagIndex();
        if (! $tagIndex) {
            return ['error' => 'Tag index not available'];
        }

        $stats = [];
        foreach ($this->tags as $tag) {
            $keys = $tagIndex->getKeysForTag($tag);
            $stats[$tag->value] = [
                'tag' => $tag->value,
                'key_count' => count($keys),
                'keys' => $keys,
            ];
        }

        return $stats;
    }

    /**
     * Get the underlying SmartCache instance
     */
    public function getCache(): SmartCache
    {
        return $this->cache;
    }

    /**
     * Get the current tags
     */
    public function getTags(): array
    {
        return $this->tags;
    }

    /**
     * Create a tagged cache instance from a regular cache
     */
    public static function make(SmartCache $cache): self
    {
        return new self($cache);
    }
}
