<?php

/**
 * Хелпер для роботи з XML
 * Методи для парсингу та генерації XML
 *
 * @package Flowaxy\Support\Helpers
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Support\Helpers;

final class XmlHelper
{
    /**
     * Парсити XML рядок
     *
     * @param string $xml
     * @param bool $assoc
     * @return \SimpleXMLElement|array<string, mixed>|false
     */
    public static function parse(string $xml, bool $assoc = true): \SimpleXMLElement|array|false
    {
        libxml_use_internal_errors(true);
        $result = simplexml_load_string($xml);

        if ($result === false) {
            return false;
        }

        if ($assoc) {
            return self::toArray($result);
        }

        return $result;
    }

    /**
     * Конвертувати SimpleXMLElement в масив
     *
     * @param \SimpleXMLElement $xml
     * @return array<string, mixed>
     */
    public static function toArray(\SimpleXMLElement $xml): array
    {
        $array = json_decode(json_encode($xml), true);

        return $array !== null ? $array : [];
    }

    /**
     * Конвертувати масив в XML
     *
     * @param array<string, mixed> $array
     * @param string $rootElement
     * @return string
     */
    public static function fromArray(array $array, string $rootElement = 'root'): string
    {
        $xml = new \SimpleXMLElement("<{$rootElement}></{$rootElement}>");
        self::arrayToXml($array, $xml);

        return $xml->asXML();
    }

    /**
     * Рекурсивно конвертувати масив в XML
     *
     * @param array<string, mixed> $data
     * @param \SimpleXMLElement $xml
     * @return void
     */
    private static function arrayToXml(array $data, \SimpleXMLElement &$xml): void
    {
        foreach ($data as $key => $value) {
            if (is_array($value)) {
                if (is_numeric($key)) {
                    $key = 'item' . $key;
                }
                $subnode = $xml->addChild($key);
                self::arrayToXml($value, $subnode);
            } else {
                if (is_numeric($key)) {
                    $key = 'item' . $key;
                }
                $xml->addChild($key, htmlspecialchars((string)$value));
            }
        }
    }

    /**
     * Перевірити, чи рядок є валідним XML
     *
     * @param string $xml
     * @return bool
     */
    public static function validate(string $xml): bool
    {
        libxml_use_internal_errors(true);
        $result = simplexml_load_string($xml);
        $errors = libxml_get_errors();
        libxml_clear_errors();

        return $result !== false && empty($errors);
    }

    /**
     * Прочитати XML з файлу
     *
     * @param string $path
     * @param bool $assoc
     * @return \SimpleXMLElement|array<string, mixed>|false
     */
    public static function readFile(string $path, bool $assoc = true): \SimpleXMLElement|array|false
    {
        if (!FileHelper::exists($path)) {
            return false;
        }

        $content = FileHelper::get($path);
        if ($content === false) {
            return false;
        }

        return self::parse($content, $assoc);
    }

    /**
     * Записати XML у файл
     *
     * @param string $path
     * @param array<string, mixed>|\SimpleXMLElement $data
     * @param string $rootElement
     * @return bool
     */
    public static function writeFile(string $path, array|\SimpleXMLElement $data, string $rootElement = 'root'): bool
    {
        if ($data instanceof \SimpleXMLElement) {
            $xml = $data->asXML();
        } else {
            $xml = self::fromArray($data, $rootElement);
        }

        if ($xml === false) {
            return false;
        }

        return FileHelper::put($path, $xml) !== false;
    }

    /**
     * Отримати атрибут з XML
     *
     * @param \SimpleXMLElement $xml
     * @param string $attribute
     * @return string|null
     */
    public static function getAttribute(\SimpleXMLElement $xml, string $attribute): ?string
    {
        $attributes = $xml->attributes();
        if ($attributes === null) {
            return null;
        }

        return isset($attributes[$attribute]) ? (string)$attributes[$attribute] : null;
    }

    /**
     * Встановити атрибут в XML
     *
     * @param \SimpleXMLElement $xml
     * @param string $attribute
     * @param string $value
     * @return void
     */
    public static function setAttribute(\SimpleXMLElement $xml, string $attribute, string $value): void
    {
        $xml->addAttribute($attribute, $value);
    }
}
