<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Cache;

use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentState;

/**
 * Cache Invalidation Strategy
 *
 * Coordinates cache invalidation across all LiveComponent cache layers.
 *
 * Invalidation Triggers:
 * - Component state changes
 * - Slot content updates
 * - Template modifications
 * - Manual invalidation requests
 *
 * Strategies:
 * - Immediate invalidation (clear cache instantly)
 * - Lazy invalidation (mark as invalidated, clear on next access)
 * - Time-based expiration (TTL)
 * - Event-driven invalidation (on specific events)
 */
final readonly class CacheInvalidationStrategy
{
    public function __construct(
        private ComponentStateCache $stateCache,
        private SlotContentCache $slotCache,
        private TemplateFragmentCache $templateCache
    ) {
    }

    /**
     * Invalidate all caches for a specific component instance
     *
     * Use when: Component state changes, component is deleted
     */
    public function invalidateComponent(ComponentId $componentId): CacheInvalidationResult
    {
        $stateInvalidated = $this->stateCache->invalidate($componentId);
        $slotInvalidated = $this->slotCache->invalidateComponent($componentId);

        return new CacheInvalidationResult(
            success: $stateInvalidated && $slotInvalidated,
            invalidated: ['state', 'slots'],
            componentId: $componentId
        );
    }

    /**
     * Invalidate only slot caches for a component
     *
     * Use when: Slot content changes but state remains same
     */
    public function invalidateComponentSlots(ComponentId $componentId): CacheInvalidationResult
    {
        $slotInvalidated = $this->slotCache->invalidateComponent($componentId);

        return new CacheInvalidationResult(
            success: $slotInvalidated,
            invalidated: ['slots'],
            componentId: $componentId
        );
    }

    /**
     * Invalidate specific slot for a component
     *
     * Use when: Single slot content changes
     */
    public function invalidateSlot(ComponentId $componentId, string $slotName): CacheInvalidationResult
    {
        $invalidated = $this->slotCache->invalidateSlot($componentId, $slotName);

        return new CacheInvalidationResult(
            success: $invalidated,
            invalidated: ["slot:{$slotName}"],
            componentId: $componentId
        );
    }

    /**
     * Invalidate all template fragments for a component type
     *
     * Use when: Template file is modified, component class changes
     */
    public function invalidateComponentType(string $componentType): CacheInvalidationResult
    {
        $templateInvalidated = $this->templateCache->invalidateComponentType($componentType);

        return new CacheInvalidationResult(
            success: $templateInvalidated,
            invalidated: ['templates'],
            componentType: $componentType
        );
    }

    /**
     * Invalidate specific template variant
     *
     * Use when: Specific variant is modified
     */
    public function invalidateVariant(string $componentType, string $variant): CacheInvalidationResult
    {
        $invalidated = $this->templateCache->invalidateVariant($componentType, $variant);

        return new CacheInvalidationResult(
            success: $invalidated,
            invalidated: ["template:{$variant}"],
            componentType: $componentType
        );
    }

    /**
     * Smart invalidation based on state change
     *
     * Compares old and new state to determine what needs invalidation
     */
    public function invalidateOnStateChange(
        ComponentId $componentId,
        ComponentState $oldState,
        ComponentState $newState
    ): CacheInvalidationResult {
        $invalidated = [];

        // Always invalidate state cache
        $this->stateCache->invalidate($componentId);
        $invalidated[] = 'state';

        // Check if state change affects slots
        if ($this->stateAffectsSlots($oldState, $newState)) {
            $this->slotCache->invalidateComponent($componentId);
            $invalidated[] = 'slots';
        }

        return new CacheInvalidationResult(
            success: true,
            invalidated: $invalidated,
            componentId: $componentId,
            reason: 'state_change'
        );
    }

    /**
     * Clear all LiveComponent caches (nuclear option)
     *
     * Use with caution! Only for: deployments, major updates, debugging
     */
    public function clearAll(): CacheInvalidationResult
    {
        $stateCleared = $this->stateCache->clear();
        $slotCleared = $this->slotCache->clear();
        $templateCleared = $this->templateCache->clear();

        return new CacheInvalidationResult(
            success: $stateCleared && $slotCleared && $templateCleared,
            invalidated: ['state', 'slots', 'templates'],
            reason: 'clear_all'
        );
    }

    /**
     * Bulk invalidation for multiple components
     *
     * @param array<ComponentId> $componentIds
     */
    public function invalidateBulk(array $componentIds): CacheInvalidationResult
    {
        $successCount = 0;

        foreach ($componentIds as $componentId) {
            $result = $this->invalidateComponent($componentId);
            if ($result->success) {
                $successCount++;
            }
        }

        return new CacheInvalidationResult(
            success: $successCount === count($componentIds),
            invalidated: ['state', 'slots'],
            reason: "bulk_invalidation:{$successCount}/" . count($componentIds)
        );
    }

    /**
     * Check if state change affects slot rendering
     */
    private function stateAffectsSlots(ComponentState $oldState, ComponentState $newState): bool
    {
        // Keys that typically affect slot rendering
        $slotAffectingKeys = [
            'sidebarWidth',
            'sidebarCollapsed',
            'isOpen',
            'padding',
            'theme',
            'variant',
        ];

        foreach ($slotAffectingKeys as $key) {
            if ($oldState->get($key) !== $newState->get($key)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get invalidation statistics across all caches
     */
    public function getStats(): array
    {
        return [
            'state_cache' => $this->stateCache->getStats(),
            'slot_cache' => $this->slotCache->getStats(),
            'template_cache' => $this->templateCache->getStats(),
        ];
    }
}

/**
 * Cache Invalidation Result Value Object
 */
final readonly class CacheInvalidationResult
{
    public function __construct(
        public bool $success,
        public array $invalidated,
        public ?ComponentId $componentId = null,
        public ?string $componentType = null,
        public ?string $reason = null
    ) {
    }

    public function isSuccess(): bool
    {
        return $this->success;
    }

    public function getInvalidatedCaches(): array
    {
        return $this->invalidated;
    }

    public function toArray(): array
    {
        return [
            'success' => $this->success,
            'invalidated' => $this->invalidated,
            'component_id' => $this->componentId?->toString(),
            'component_type' => $this->componentType,
            'reason' => $this->reason,
        ];
    }
}
