<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator\Integrations\Caching;

use Ekesto\CacheInvalidator\Integrations\Caching\Adapters\BreezeAdapter;
use Ekesto\CacheInvalidator\Integrations\Caching\Adapters\CacheEnablerAdapter;
use Ekesto\CacheInvalidator\Integrations\Caching\Adapters\FlyingPressAdapter;
use Ekesto\CacheInvalidator\Integrations\Caching\Adapters\LiteSpeedCacheAdapter;
use Ekesto\CacheInvalidator\Integrations\Caching\Adapters\WpFastestCacheAdapter;
use Ekesto\CacheInvalidator\Integrations\Caching\Adapters\WpRocketAdapter;
use Ekesto\CacheInvalidator\InvalidatorRuntime;
use Ekesto\CacheInvalidator\Support\Debug;

/**
 * Adapter manager.
 *
 * Chooses the best available cache adapter.
 */
final class AdapterManager
{
    /**
     * @var CacheAdapterInterface[]|null
     */
    private static ?array $adapters = null;

    /**
     * Returns all known adapters.
     *
     * @return CacheAdapterInterface[]
     */
    public static function getAdapters(): array
    {
        if (self::$adapters !== null) {
            return self::$adapters;
        }

        self::$adapters = [
            // Only adapters with deterministic URL-level purge support are registered.
            // Legacy / non-deterministic adapters intentionally excluded.
            new LiteSpeedCacheAdapter(),
            new WpRocketAdapter(),
            new FlyingPressAdapter(),
            new CacheEnablerAdapter(),
            new BreezeAdapter(),
            // new WpFastestCacheAdapter(),
            // Future adapters go here.
        ];

        return self::$adapters;
    }

    /**
     * Registers adapter hooks.
     *
     * @return void
     */
    public static function registerHooks(): void
    {
        foreach (self::getAdapters() as $adapter) {
            $adapter->registerHooks();
        }
    }

    /**
     * Returns the first available adapter.
     *
     * @return CacheAdapterInterface|null
     */
    public static function getActiveAdapter(): ?CacheAdapterInterface
    {
        foreach (self::getAdapters() as $adapter) {
            if ($adapter->isAvailable()) {
                return $adapter;
            }
        }

        return null;
    }

    /**
     * Returns whether any adapter is available.
     *
     * @return bool
     */
    public static function hasAvailableAdapters(): bool
    {
        return self::getActiveAdapter() !== null;
    }

    /**
     * Returns cache-enabled status for the active adapter when detectable.
     *
     * @return bool|null True/false when detectable; null when unsupported or absent.
     */
    public static function getActiveAdapterCacheEnabledStatus(): ?bool
    {
        $adapter = self::getActiveAdapter();
        if ($adapter === null || !$adapter->supportsCacheStatusDetection()) {
            return null;
        }

        try {
            return (bool) $adapter->isCacheEnabled();
        } catch (\Throwable $throwable) {
            return null;
        }
    }

    /**
     * Returns the active adapter only when operational right now.
     *
     * @return CacheAdapterInterface|null
     */
    public static function getOperationalAdapter(): ?CacheAdapterInterface
    {
        $adapter = self::getActiveAdapter();
        if ($adapter === null) {
            return null;
        }

        $cacheEnabled = self::getActiveAdapterCacheEnabledStatus();
        if ($cacheEnabled === false) {
            return null;
        }

        return $adapter;
    }

    /**
     * Returns whether the active adapter is operational with filter override support.
     *
     * Context is merged with runtime adapter details and passed to:
     * - apply_filters('ekesto_ci_force_operational_adapter', null, $context)
     * - apply_filters('ekesto_ci_has_operational_adapter', $result, $context)
     *
     * `ekesto_ci_force_operational_adapter` is a test-only hard override hook.
     * It is request-scoped and must not cache state.
     *
     * Example (non-production only):
     * add_filter('ekesto_ci_force_operational_adapter', function ($override, $context) {
     *     if (wp_get_environment_type() !== 'production') {
     *         return true;
     *     }
     *     return $override;
     * }, 10, 2);
     *
     * @param array<string,mixed> $context
     * @return bool
     */
    public static function hasOperationalAdapter(array $context = []): bool
    {
        $adapter = self::getActiveAdapter();
        $detectionSupported = $adapter !== null ? $adapter->supportsCacheStatusDetection() : false;
        $cacheEnabled = $detectionSupported ? self::getActiveAdapterCacheEnabledStatus() : null;
        $result = ($adapter !== null) && ($cacheEnabled !== false);

        $baseContext = [
            'caller' => '',
            'reason' => '',
            'adapter' => $adapter !== null ? $adapter->getKey() : '',
            'adapter_label' => $adapter !== null ? $adapter->getLabel() : '',
            'adapter_available' => $adapter !== null,
            'cache_enabled' => $cacheEnabled,
            'detection_supported' => $detectionSupported,
        ];

        $mergedContext = array_merge($baseContext, $context);

        if (has_filter('ekesto_ci_force_operational_adapter')) {
            try {
                // Callbacks must not re-enter hasOperationalAdapter(); this hook is terminal when non-null.
                $override = apply_filters('ekesto_ci_force_operational_adapter', null, $mergedContext);
            } catch (\Throwable $throwable) {
                Debug::warn('eci_operational_adapter_force_override_exception', [
                    'error' => $throwable->getMessage(),
                    'caller' => (string) ($mergedContext['caller'] ?? ''),
                    'reason' => (string) ($mergedContext['reason'] ?? ''),
                ]);
                return false;
            }

            if ($override !== null) {
                Debug::log('eci_operational_adapter_forced', [
                    'result' => (bool) $override,
                    'caller' => (string) ($mergedContext['caller'] ?? ''),
                    'reason' => (string) ($mergedContext['reason'] ?? ''),
                ]);
                return (bool) $override;
            }
        }

        try {
            $filtered = apply_filters('ekesto_ci_has_operational_adapter', $result, $mergedContext);
            return (bool) $filtered;
        } catch (\Throwable $throwable) {
            return false;
        }
    }

    /**
     * Returns a diagnostic label for the active adapter.
     *
     * @return string
     */
    public static function getActiveAdapterLabel(): string
    {
        $adapter = self::getActiveAdapter();
        if ($adapter === null) {
            return __('None (no supported caching plugin detected)', 'cache-autopilot');
        }

        return $adapter->getLabel();
    }
}
