<?php
declare(strict_types=1);

namespace ROITheme\Shared\Domain\ValueObjects;

/**
 * Value Object: Exclusion por patron URL
 *
 * Evalua si la URL actual coincide con alguno de los patrones configurados.
 * Soporta:
 * - Substring simple: "/privado/" coincide con cualquier URL que contenga ese texto
 * - Regex: Patrones que empiezan y terminan con "/" son evaluados como regex
 *
 * @package ROITheme\Shared\Domain\ValueObjects
 */
final class UrlPatternExclusion extends ExclusionRule
{
    /**
     * @param array<string> $urlPatterns Lista de patrones (substring o regex)
     */
    public function __construct(
        private readonly array $urlPatterns = []
    ) {}

    /**
     * {@inheritdoc}
     *
     * Contexto esperado:
     * - request_uri: string (URI del request)
     * - url: string (URL completa, opcional)
     */
    public function matches(array $context): bool
    {
        if (!$this->hasValues()) {
            return false;
        }

        $requestUri = $context['request_uri'] ?? '';
        $url = $context['url'] ?? '';

        if ($requestUri === '' && $url === '') {
            return false;
        }

        foreach ($this->urlPatterns as $pattern) {
            if ($this->matchesPattern($pattern, $requestUri, $url)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Evalua si un patron coincide con el request_uri o url
     */
    private function matchesPattern(string $pattern, string $requestUri, string $url): bool
    {
        // Detectar si es regex (empieza con /)
        if ($this->isRegex($pattern)) {
            return $this->matchesRegex($pattern, $requestUri);
        }

        // Substring matching
        return $this->matchesSubstring($pattern, $requestUri, $url);
    }

    /**
     * Detecta si el patron es una expresion regular
     */
    private function isRegex(string $pattern): bool
    {
        // Un patron regex debe empezar con / y terminar con / (posiblemente con flags)
        return preg_match('#^/.+/[gimsux]*$#', $pattern) === 1;
    }

    /**
     * Evalua coincidencia regex
     */
    private function matchesRegex(string $pattern, string $subject): bool
    {
        // Suprimir warnings de regex invalidos
        $result = @preg_match($pattern, $subject);

        return $result === 1;
    }

    /**
     * Evalua coincidencia por substring o wildcard
     *
     * Soporta wildcards simples:
     * - `*sct*` coincide con URLs que contengan "sct"
     * - `*` se convierte a `.*` en regex
     * - Sin wildcards: busca substring literal
     */
    private function matchesSubstring(string $pattern, string $requestUri, string $url): bool
    {
        // Detectar si tiene wildcards (*)
        if (str_contains($pattern, '*')) {
            return $this->matchesWildcard($pattern, $requestUri, $url);
        }

        // Substring literal
        if ($requestUri !== '' && str_contains($requestUri, $pattern)) {
            return true;
        }

        if ($url !== '' && str_contains($url, $pattern)) {
            return true;
        }

        return false;
    }

    /**
     * Evalua coincidencia con patron wildcard
     *
     * Convierte wildcards (*) a regex (.*)
     */
    private function matchesWildcard(string $pattern, string $requestUri, string $url): bool
    {
        // Convertir wildcard a regex:
        // 1. Escapar caracteres especiales de regex (excepto *)
        // 2. Convertir * a .*
        $regexPattern = preg_quote($pattern, '#');
        $regexPattern = str_replace('\\*', '.*', $regexPattern);
        $regexPattern = '#' . $regexPattern . '#i';

        if ($requestUri !== '' && preg_match($regexPattern, $requestUri) === 1) {
            return true;
        }

        if ($url !== '' && preg_match($regexPattern, $url) === 1) {
            return true;
        }

        return false;
    }

    public function hasValues(): bool
    {
        return !empty($this->urlPatterns);
    }

    public function serialize(): string
    {
        return json_encode($this->urlPatterns, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    }

    /**
     * @return array<string>
     */
    public function getUrlPatterns(): array
    {
        return $this->urlPatterns;
    }

    /**
     * Crea instancia desde JSON
     */
    public static function fromJson(string $json): self
    {
        $decoded = json_decode($json, true);

        if (!is_array($decoded)) {
            return self::empty();
        }

        // Filtrar valores vacios
        $patterns = array_filter($decoded, fn($p): bool => is_string($p) && $p !== '');

        return new self(array_values($patterns));
    }

    /**
     * Crea instancia vacia
     */
    public static function empty(): self
    {
        return new self([]);
    }
}
