<?php

namespace DevOwl\RealCookieBanner\Vendor\DevOwl\FastHtmlTag;

use DevOwl\RealCookieBanner\Vendor\DevOwl\FastHtmlTag\finder\AbstractFinder;
use DevOwl\RealCookieBanner\Vendor\DevOwl\FastHtmlTag\finder\match\AbstractMatch;
/**
 * Initialize a new parser.
 * @internal
 */
class FastHtmlTag
{
    /**
     * Unique name of this instance.
     *
     * Can be useful in conjunction with `$$skipFastHtmlTag`.
     *
     * @var string
     */
    private $name;
    /**
     * Callbacks.
     *
     * @var callable[]
     */
    private $callbacks = [];
    /**
     * Allows to rerun the processor on the resulting HTML again.
     */
    private $rerun = \false;
    /**
     * Callbacks for `SelectorSyntaxAttributeFunction`.
     *
     * @var callable[]
     */
    private $selectorSyntaxFunctions = [];
    /**
     * See `AbstractFinder`.
     *
     * @var AbstractFinder[]
     */
    private $finder = [];
    /**
     * C'tor.
     *
     * @param string $name
     */
    public function __construct($name = 'FastHtmlTag')
    {
        $this->name = $name;
    }
    /**
     * Add a finder scheme. See `finder/` for available ones.
     *
     * @param AbstractFinder $finder
     */
    public function addFinder($finder)
    {
        $this->finder[] = $finder;
    }
    /**
     * Add a callable. The first parameter is the HTML string and should return HTML.
     *
     * @param callable $callback
     */
    public function addCallback($callback)
    {
        $this->callbacks[] = $callback;
    }
    /**
     * Add a callable for a Selector Syntax function.
     *
     * The callback gets the following parameters and expects `boolean` as result:
     *
     * `SelectorSyntaxAttributeFunction $function, AbstractMatch $match, mixed $value`
     *
     * @param string $functionName
     * @param callable $callback
     */
    public function addSelectorSyntaxFunction($functionName, $callback)
    {
        $this->selectorSyntaxFunctions[$functionName] = $callback;
    }
    /**
     * Allows to parse and modify any content. This could be e.g. a JSON string
     * (each value gets iterated and parsed if it is a HTML).
     *
     * @param mixed $mixed
     */
    public function modifyAny($mixed)
    {
        $json = Utils::isJson($mixed, \false, \false);
        // Avoid JSON primitives to be replaced
        if (\is_int($json) || $json === \true || \is_float($json) || Utils::isBinary($mixed)) {
            return $mixed;
        }
        if ($json !== \false) {
            // Is it a primitive JSON string?
            if (\is_string($json)) {
                return \json_encode($this->modifyAny($json));
            }
            // We have now a complete JSON array, let's walk it recursively and apply content blocker
            if ($json !== null) {
                if (isset($json->{'$$skipFastHtmlTag'}) && \is_array($json->{'$$skipFastHtmlTag'}) && \in_array($this->name, $json->{'$$skipFastHtmlTag'}, \true)) {
                    return $mixed;
                }
                Utils::array_and_object_walk_recursive($json, function (&$value) {
                    if (Utils::isHtml($value)) {
                        $value = $this->modifyHtml($value);
                    }
                });
            }
            if (\is_array($mixed) || \is_object($mixed)) {
                // The original passed parameter is an array or object, so we return the modified JSON array or object
                return $json;
            }
            return \json_encode($json);
        } elseif (\is_string($mixed)) {
            // Usual string
            return $this->modifyHtml($mixed);
        }
        // @codeCoverageIgnoreStart
        return $mixed;
        // @codeCoverageIgnoreEnd
    }
    /**
     * Allow to parse and modify a given HTML string.
     *
     * @param string $html
     */
    public function modifyHtml($html)
    {
        // With our complex regular expressions, `preg_replace[_callback]` can sometimes lead
        // to `PREG_BACKTRACK_LIMIT_ERROR` errors with large strings. Unfortunately, we can only
        // fix this by setting the backtrack limit to a very high value via PHP configuration (`php.ini`)
        $originalBacktrackLimit = \function_exists('ini_get') ? \ini_get('pcre.backtrack_limit') : \false;
        $canModifyBacktrackLimit = $originalBacktrackLimit !== \false && \wp_is_ini_value_changeable('pcre.backtrack_limit');
        if ($canModifyBacktrackLimit) {
            // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
            @\ini_set('pcre.backtrack_limit', '10000000');
            // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
        }
        foreach ($this->finder as $finder) {
            $finder->setFastHtmlTag($this);
            $html = $finder->replace($html);
        }
        if ($canModifyBacktrackLimit) {
            // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
            @\ini_set('pcre.backtrack_limit', $originalBacktrackLimit);
            // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
        }
        foreach ($this->callbacks as $callback) {
            $html = $callback($html);
        }
        if ($this->rerun) {
            $this->rerun = \false;
            $html = $this->modifyHtml($html);
        }
        // Remove invisible attributes (https://regex101.com/r/QAy0R0/2)
        $html = \preg_replace(\sprintf('/\\s+%s[^\\s>\\/]+/m', AbstractMatch::HTML_ATTRIBUTE_INVISIBLE_PREFIX), '', $html);
        return $html;
    }
    /**
     * Get a defined selector syntax function by name.
     *
     * @param string $functionName
     */
    public function getSelectorSyntaxFunction($functionName)
    {
        return $this->selectorSyntaxFunctions[$functionName] ?? null;
    }
    /**
     * Allows to rerun the processor on the resulting HTML again.
     */
    public function registerRerun()
    {
        $this->rerun = \true;
    }
}
