<?php

namespace Dalten\RuianToUir\Converter;

use Dalten\RuianToUir\Converter\Result\ExplorerResult;
use Dalten\RuianToUir\Converter\Result\ResultInterface;

/**
 * Obaluje converter tak, aby z něj mohl dostat informace o vnitřní struktuře prvku.
 *
 * Schválně neimplementuje ConverterInterface, protože jeho data mají jinou strukturu.
 */
readonly class ExplorerWrapper
{
    public function __construct(private ConverterInterface $innerConverter)
    {
    }

    /**
     * Zjišťuje, zda tato třída dokáže obsloužit daný element.
     *
     * @param \XMLReader $document instance readeru s kurzorem na testovaném prvku
     *
     * @return bool Obsluhuje tato třída daný prvek?
     */
    public function accepts(\XMLReader $document): bool
    {
        return $this->innerConverter->accepts($document);
    }

    /**
     * Převádí informace z prvku pod do zjednodušeného formátu.
     *
     * @param \XMLReader $document instance readeru s kurzorem na testovaném prvku
     */
    public function convert(\XMLReader $document): ResultInterface
    {
        $name = $document->name;
        $element = $this->getNextElement($document, $name);

        if ($element instanceof \XMLReader && $element->name === $name) {
            $this->getNextElement($document, $name);
        }

        $data = $this->getValue($document, $name);

        if (!\is_array($data)) {
            throw new \RuntimeException('Nepodařilo se získat data.');
        }

        return new ExplorerResult($this->innerConverter->getName(), $data);
    }

    /**
     * @return string|array<string, mixed>
     */
    private function getValue(\XMLReader $document, string $tagName): array|string
    {
        $output = [];

        $simpleValue = $document->readString();

        while ($this->getNextElement($document, $tagName)) {
            if (\XMLReader::ELEMENT === $document->nodeType) {
                $name = $baseName = $document->name;
                $i = 0;
                while (\array_key_exists($name, $output)) {
                    $name = $baseName.'#'.++$i;
                }
                if ($document->isEmptyElement) {
                    $output[$name] = null;
                } else {
                    $output[$name] = $this->getValue($document, $document->name);
                }
            }
        }

        return $output ?: $simpleValue;
    }

    /**
     * Slouží k nalezení dalšího prvku. Hledá prvek a ignoruje všechny ostatní typy XML uzlů.
     *
     * @param \XMLReader $document     instance readeru s kurzorem na testovaném prvku
     * @param string     $outerTagName název obalového tagu
     *
     * @return false|\XMLReader XMLreader s nastaveným kurzorem a nebo false, pokud bychom opustili obsluhovaný prvek
     */
    protected function getNextElement(\XMLReader $document, string $outerTagName): false|\XMLReader
    {
        $document->read();
        while (\XMLReader::ELEMENT !== $document->nodeType) {
            if ($document->name === $outerTagName && \XMLReader::END_ELEMENT === $document->nodeType) {
                return false;
            }
            $document->read();
        }

        return $document;
    }

    /**
     * Vrátí název vnitřního converteru.
     *
     * @return string název converteru
     */
    public function getName(): string
    {
        return 'Explorer:'.$this->innerConverter->getName();
    }
}
