<?php

namespace WPForms\Vendor\enshrined\svgSanitize\ElementReference;

use WPForms\Vendor\enshrined\svgSanitize\data\XPath;
use WPForms\Vendor\enshrined\svgSanitize\Exceptions\NestingException;
use WPForms\Vendor\enshrined\svgSanitize\Helper;
class Resolver
{
    /**
     * @var XPath
     */
    protected $xPath;
    /**
     * @var Subject[]
     */
    protected $subjects = [];
    /**
     * @var array DOMElement[]
     */
    protected $elementsToRemove = [];
    /**
     * @var int
     */
    protected $useNestingLimit;
    public function __construct(XPath $xPath, $useNestingLimit)
    {
        $this->xPath = $xPath;
        $this->useNestingLimit = $useNestingLimit;
    }
    public function collect()
    {
        $this->collectIdentifiedElements();
        $this->processReferences();
        $this->determineInvalidSubjects();
    }
    /**
     * Resolves one subject by element.
     *
     * @param \DOMElement $element
     * @param bool $considerChildren Whether to search in Subject's children as well
     * @return Subject|null
     */
    public function findByElement(\DOMElement $element, $considerChildren = \false)
    {
        foreach ($this->subjects as $subject) {
            if ($element === $subject->getElement() || $considerChildren && Helper::isElementContainedIn($element, $subject->getElement())) {
                return $subject;
            }
        }
        return null;
    }
    /**
     * Resolves subjects (plural!) by element id - in theory malformed
     * DOM might have same ids assigned to different elements and leaving
     * it to client/browser implementation which element to actually use.
     *
     * @param string $elementId
     * @return Subject[]
     */
    public function findByElementId($elementId)
    {
        return \array_filter($this->subjects, function (Subject $subject) use($elementId) {
            return $elementId === $subject->getElementId();
        });
    }
    /**
     * Collects elements having `id` attribute (those that can be referenced).
     */
    protected function collectIdentifiedElements()
    {
        /** @var \DOMNodeList|\DOMElement[] $elements */
        $elements = $this->xPath->query('//*[@id]');
        foreach ($elements as $element) {
            $this->subjects[$element->getAttribute('id')] = new Subject($element, $this->useNestingLimit);
        }
    }
    /**
     * Processes references from and to elements having `id` attribute concerning
     * their occurrence in `<use ... xlink:href="#identifier">` statements.
     */
    protected function processReferences()
    {
        $useNodeName = $this->xPath->createNodeName('use');
        foreach ($this->subjects as $subject) {
            $useElements = $this->xPath->query($useNodeName . '[@href or @xlink:href]', $subject->getElement());
            /** @var \DOMElement $useElement */
            foreach ($useElements as $useElement) {
                $useId = Helper::extractIdReferenceFromHref(Helper::getElementHref($useElement));
                if ($useId === null || !isset($this->subjects[$useId])) {
                    continue;
                }
                $subject->addUse($this->subjects[$useId]);
                $this->subjects[$useId]->addUsedIn($subject);
            }
        }
    }
    /**
     * Determines and tags infinite loops.
     */
    protected function determineInvalidSubjects()
    {
        foreach ($this->subjects as $subject) {
            if (\in_array($subject->getElement(), $this->elementsToRemove)) {
                continue;
            }
            $useId = Helper::extractIdReferenceFromHref(Helper::getElementHref($subject->getElement()));
            try {
                if ($useId === $subject->getElementId()) {
                    $this->markSubjectAsInvalid($subject);
                } elseif ($subject->hasInfiniteLoop()) {
                    $this->markSubjectAsInvalid($subject);
                }
            } catch (NestingException $e) {
                $this->elementsToRemove[] = $e->getElement();
                $this->markSubjectAsInvalid($subject);
            }
        }
    }
    /**
     * Get all the elements that caused a nesting exception.
     *
     * @return array
     */
    public function getElementsToRemove()
    {
        return $this->elementsToRemove;
    }
    /**
     * The Subject is invalid for some reason, therefore we should
     * remove it and all it's child usages.
     *
     * @param Subject $subject
     */
    protected function markSubjectAsInvalid(Subject $subject)
    {
        $this->elementsToRemove = \array_merge($this->elementsToRemove, $subject->clearInternalAndGetAffectedElements());
    }
}
