<?php

declare(strict_types=1);

namespace FleekDash\Core\ResourceBlocker;

/**
 * Shared context for the ResourceBlocker system.
 *
 * Centralises configuration, caching and helper methods so that other
 * components (QueueGuard, InlineGuard, etc.) can remain lean and focused.
 */
class BlockerContext
{
    private array $allowedStyles;
    private array $allowedScripts;
    /** @var callable */
    private $conditionCallback;
    private array $blockPatterns;
    private bool $blockThirdParty;
    private array $allowedPaths;
    private array $customAllowedPaths;

    /** @var array<string,bool> */
    private array $shouldBlockCache = [];
    private array $patternMatchCache = [];
    private ?string $lastPage = null;

    public function __construct(array $config = [])
    {
        $this->allowedStyles = $config['allowed_styles'] ?? [];
        $this->allowedScripts = $config['allowed_scripts'] ?? [];
        $this->conditionCallback = $config['condition'] ?? static function (): bool {
            return true;
        };
        $this->blockPatterns = $config['block_patterns'] ?? [];
        $this->blockThirdParty = (bool) ($config['block_third_party'] ?? false);

        $this->customAllowedPaths = array_values(array_unique($config['allowed_paths'] ?? []));
        $this->allowedPaths = array_values(array_unique(array_merge(
            $this->customAllowedPaths,
            [
                '/wp-admin/',
                '/wp-includes/',
                'load-scripts.php',
                'load-styles.php',
            ]
        )));

        $this->resetCaches();
    }

    public function shouldBlock(): bool
    {
        $currentPage = $_GET['page'] ?? '';

        if ($this->lastPage !== null && $this->lastPage !== $currentPage) {
            $this->shouldBlockCache = [];
        }
        $this->lastPage = $currentPage;

        $cacheKey = serialize([
            'is_admin' => is_admin(),
            'page' => $currentPage,
            'patterns_hash' => md5(serialize($this->blockPatterns)),
            'callback_signature' => $this->getCallbackSignature(),
        ]);

        $useCache = $this->isCacheFriendlyCallback();
        if ($useCache && isset($this->shouldBlockCache[$cacheKey])) {
            return $this->shouldBlockCache[$cacheKey];
        }

        if (!is_admin()) {
            if ($useCache) {
                $this->shouldBlockCache[$cacheKey] = false;
            }
            return false;
        }

        $conditionResult = is_callable($this->conditionCallback)
            ? (bool) call_user_func($this->conditionCallback)
            : false;

        if (!$conditionResult) {
            if ($useCache) {
                $this->shouldBlockCache[$cacheKey] = false;
            }
            return false;
        }

        $patternMatch = $this->matchesBlockPattern($currentPage);

        if ($useCache) {
            $this->shouldBlockCache[$cacheKey] = $patternMatch;
        }

        return $patternMatch;
    }

    public function shouldBlockThirdParty(): bool
    {
        return $this->blockThirdParty;
    }

    public function getAllowedStyles(): array
    {
        return $this->allowedStyles;
    }

    public function getAllowedScripts(): array
    {
        return $this->allowedScripts;
    }

    public function isAllowedStyleHandle(string $handle): bool
    {
        return in_array($handle, $this->allowedStyles, true);
    }

    public function isAllowedScriptHandle(string $handle): bool
    {
        return in_array($handle, $this->allowedScripts, true);
    }

    public function isAllowedPath(string $src): bool
    {
        foreach ($this->allowedPaths as $allowedPath) {
            if (strpos($src, $allowedPath) !== false) {
                return true;
            }
        }

        return false;
    }

    public function isThirdPartyScript(string $src): bool
    {
        static $cache = [];

        if (isset($cache[$src])) {
            return $cache[$src];
        }

        if (empty($src) || strpos($src, 'http') !== 0) {
            $cache[$src] = false;
            return false;
        }

        if ($this->isAllowedPath($src)) {
            $cache[$src] = false;
            return false;
        }

        $wpContentUrl = \content_url();

        $result = strpos($src, $wpContentUrl) !== false;
        $cache[$src] = $result;

        return $result;
    }

    public function getBlockPatterns(): array
    {
        return $this->blockPatterns;
    }

    public function getCustomAllowedPaths(): array
    {
        return $this->customAllowedPaths;
    }

    private function matchesBlockPattern(string $currentPage): bool
    {
        $patternsHash = md5(serialize($this->blockPatterns));
        $cacheKey = $currentPage . '_' . $patternsHash;

        if (isset($this->patternMatchCache[$cacheKey])) {
            return $this->patternMatchCache[$cacheKey];
        }

        if (empty($this->blockPatterns)) {
            $result = true;
        } else {
            $result = false;
            foreach ($this->blockPatterns as $pattern) {
                if (strpos($currentPage, $pattern) === 0) {
                    $result = true;
                    break;
                }
            }
        }

        $this->patternMatchCache[$cacheKey] = $result;
        return $result;
    }

    private function getCallbackSignature(): string
    {
        if (!is_callable($this->conditionCallback)) {
            return 'not-callable';
        }

        if (is_array($this->conditionCallback)) {
            return get_class($this->conditionCallback[0]) . '::' . $this->conditionCallback[1];
        }

        if ($this->conditionCallback instanceof \Closure) {
            return spl_object_hash($this->conditionCallback);
        }

        return (string) $this->conditionCallback;
    }

    private function isCacheFriendlyCallback(): bool
    {
        if (!is_callable($this->conditionCallback)) {
            return false;
        }

        if ($this->conditionCallback instanceof \Closure) {
            $reflection = new \ReflectionFunction($this->conditionCallback);
            return $reflection->getNumberOfParameters() === 0;
        }

        return true;
    }

    public function addBlockPattern(string $pattern): void
    {
        if (in_array($pattern, $this->blockPatterns, true)) {
            return;
        }

        $this->blockPatterns[] = $pattern;
        $this->resetCaches();
    }

    public function addBlockPatterns(array $patterns): void
    {
        foreach ($patterns as $pattern) {
            $this->addBlockPattern($pattern);
        }
    }

    public function removeBlockPattern(string $pattern): void
    {
        $index = array_search($pattern, $this->blockPatterns, true);
        if ($index === false) {
            return;
        }

        unset($this->blockPatterns[$index]);
        $this->blockPatterns = array_values($this->blockPatterns);
        $this->resetCaches();
    }

    private function resetCaches(): void
    {
        $this->shouldBlockCache = [];
        $this->patternMatchCache = [];
        $this->lastPage = null;
    }
}
