<?php declare(strict_types=1);

namespace App\Tool;

use Exception;
use Pimcore\Bundle\StaticRoutesBundle\Model\Staticroute;
use Pimcore\Model\Asset;
use Pimcore\Model\DataObject;
use Pimcore\Model\Document;
use Pimcore\Model\Site;
use Pimcore\Cache;
use Pimcore\Config;
use Pimcore\File;
use Pimcore\Model\Document\Page;
use Pimcore\Model\Document\Service;
use Pimcore\Tool;

class Utils
{
    const DOCUMENT_ROOT = 2;
    const DOCUMENT_ACCOUNT = 12;
    const SALT = 'hopcipcibirybcibirybhopaa';
    public static ?string $defaultLanguage = null;
    private static bool $inheritance;
    private static bool $fallback;
    private static ?array $languageSwitch = null;

    public static function getDefaultLanguage(): string
    {
        if (self::$defaultLanguage === null) {
            $config = Config::getSystemConfiguration();
            self::$defaultLanguage = $config['general']['default_language'] ?? current(explode(',', $config['general']['valid_languages']));
        }

        return self::$defaultLanguage;
    }

    public static function getValidFrontendLanguages(): array
    {
        $config = Config::getWebsiteConfig();
        $allowed = explode(',', $config['frontendLanguages']);

        return array_filter(Tool::getValidLanguages(), fn ($language) => in_array($language, $allowed));
    }

    public static function getDocumentTranslation($document, string $language)
    {
        if ($language == $document->getProperty('language')) {
            return $document;
        }

        $service = new Service();
        $translations = $service->getTranslations($document);

        return isset($translations[$language]) ? Page::getById($translations[$language]) : null;
    }

    public static function getTranslatedDocumentByPath(string $documentPath, string $language)
    {
        $document = Page::getByPath($documentPath);
        return $document ? self::getDocumentTranslation($document, $language) : null;
    }

    public static function objectsToOptions(array $objects, string $language, bool $forAdmin = false): array
    {
        return array_map(fn ($object) => $forAdmin ? ['key' => $object->getName($language), 'value' => (string)$object->getId()] : [(string)$object->getId() => $object->getName($language)], $objects);
    }

    public static function arrayToOptions(array $array, string $language, bool $forAdmin = false): array
    {
        return array_map(fn ($key, $item) => $forAdmin ? ['key' => $item, 'value' => (string)$key] : [(string)$key => $item], array_keys($array), $array);
    }

    public static function thumbnail($image, string $thumbnail, $fallbackImage = null): string
    {
        return match (true) {
            $image instanceof Document\Editable\Image && $image->getImage() => (string)$image->getThumbnail($thumbnail),
            $image instanceof DataObject\Data\Hotspotimage && $image->getImage() => (string)$image->getThumbnail($thumbnail),
            $image instanceof Asset\Image => (string)$image->getThumbnail($thumbnail),
            $fallbackImage instanceof Asset\Image => (string)$fallbackImage->getThumbnail($thumbnail),
            $fallbackImage => (string)(Asset\Image::getById($fallbackImage) ?: Asset\Image::getByPath($fallbackImage))?->getThumbnail($thumbnail),
            default => '',
        };
    }

    public static function image($image, string $thumbnail, $fallbackImage = null, array $imgOptions = [], array $pictureOptions = []): string
    {
        $options = ['imgAttributes' => $imgOptions, 'pictureAttributes' => $pictureOptions];

        $imageThumbnail = match (true) {
            $image instanceof Document\Editable\Image && $image->getImage() => $image->getThumbnail($thumbnail),
            $image instanceof Asset\Image || $image instanceof DataObject\Data\Hotspotimage && $image->getImage() => $image->getThumbnail($thumbnail),
            $fallbackImage instanceof Asset\Image => $fallbackImage->getThumbnail($thumbnail),
            $fallbackImage => (Asset\Image::getById($fallbackImage) ?: Asset\Image::getByPath($fallbackImage))?->getThumbnail($thumbnail),
            default => null,
        };

        return $imageThumbnail ? $imageThumbnail->getHtml($options) : '';
    }

    public static function link($link, array $additionalAttributes = [], string $text = null): string
    {
        if ($link instanceof Document\Editable\Link || $link instanceof DataObject\Data\Link) {
            $link->setValues($additionalAttributes);
        }

        $tag = (string)$link;

        return $text ? preg_replace('#(<a.*?>).*?(</a>)#', '$1' . $text . '$2', $tag) : $tag;
    }

    /**
     * @throws Exception
     */
    public static function url($staticRoute, array $params = []): string
    {
        if (!$staticRoute instanceof Staticroute) {
            $staticRoute = Staticroute::getByName($staticRoute, Site::isSiteRequest() ? Site::getCurrentSite()->getId() : 0);
        }

        return $staticRoute ? $staticRoute->assemble($params, true) : '/';
    }

    /**
     * @throws Exception
     */
    public static function docUrl(string $realFullPath, string $language, Page $rootDoc = null): string
    {
        $languageSwitch = self::getLanguageSwitch($language, $realFullPath, $rootDoc);
        return $languageSwitch[$language] ?? '/';
    }

    /**
     * @throws Exception
     */
    public static function docUrlById(int $docId, string $language, Page $rootDoc = null)
    {
        $doc = Page::getById($docId);
        if ($doc) {
            $languageSwitch = self::getLanguageSwitch($language, $doc->getFullPath(), $rootDoc);
            return $languageSwitch[$language] ?? '/';
        }
        return false;
    }

    /**
     * @throws Exception
     */
    public static function getLanguageSwitch(string $language, string $section, Page $rootDoc = null, bool $hpFallback = false): array
    {
        $rootDoc = $rootDoc ?: Page::getById(1);
        $siteId = Site::isSiteRequest() ? Site::getCurrentSite()->getRootId() : $rootDoc->getId();
        $cacheKey = 'language_switcher_' . $rootDoc->getId() . '_' . $siteId;
        $switcher = self::$languageSwitch[$rootDoc->getId()] ?? Cache::load($cacheKey);

        if ($switcher === false) {
            $switcher = [];
            $originLanguage = $rootDoc->getProperty('language');
            $queue = [$rootDoc];
            $service = new Service();

            while ($queue) {
                $doc = array_shift($queue);
                foreach ($doc->getChildren(true) as $child) {
                    $queue[] = $child;
                }
                if (!$doc->getPublished()) continue;

                $docRealFullPath = $doc->getRealFullPath();
                $translations = $service->getTranslations($doc);
                $translations[$originLanguage] = $translations[$originLanguage] ?? $doc->getId();

                foreach ($translations as $language => $translationDocId) {
                    $translationDoc = Document::getById($translationDocId);
                    if ($translationDoc && $translationDoc->isPublished()) {
                        $switcher[$docRealFullPath][$language] = $translationDoc->getFullPath();
                    }
                }
            }

            if (!Tool::isFrontendRequestByAdmin()) {
                Cache::save($switcher, $cacheKey, ['output']);
            }
        }

        self::$languageSwitch[$rootDoc->getId()] = $switcher;
        $sectionSwitch = $switcher[$section] ?? [];

        if ($hpFallback && is_array($switcher)) {
            $langParamPosition = stripos($section, '/' . $language . '/');
            if ($langParamPosition !== false) {
                $hpKey = substr($section, 0, $langParamPosition) . '/' . $language;
                $sectionSwitch += $switcher[$hpKey] ?? [];
            }
        }

        return $sectionSwitch;
    }

    public static function elasticSearchEnabled(): bool
    {
        return class_exists('\\Elasticsearch\\Client');
    }

    public static function forceInheritanceAndFallbackValues(bool $inherit = true): void
    {
        self::$inheritance = DataObject::getGetInheritedValues();
        self::$fallback = DataObject\Localizedfield::getGetFallbackValues();
        DataObject::setGetInheritedValues($inherit);
        DataObject\Localizedfield::setGetFallbackValues($inherit);
    }

    public static function restoreInheritanceAndFallbackValues(): void
    {
        DataObject::setGetInheritedValues(self::$inheritance);
        DataObject\Localizedfield::setGetFallbackValues(self::$fallback);
    }

    public static function loadAssets($rootFolderOrAssetArray = [], array $allowedTypes = ['image']): array
    {
        $queue = $rootFolderOrAssetArray instanceof Asset\Folder ? [$rootFolderOrAssetArray] : (is_array($rootFolderOrAssetArray) ? $rootFolderOrAssetArray : [Asset\Folder::getById($rootFolderOrAssetArray)]);
        $assets = [];

        while ($asset = array_shift($queue)) {
            if (!$asset instanceof Asset) continue;

            if ($asset instanceof Asset\Folder) {
                $queue = array_merge($queue, $asset->getChildren());
            } elseif (in_array($asset->getType(), $allowedTypes)) {
                $assets[] = $asset;
            }
        }

        return $assets;
    }

    public static function webalize(string $path): string
    {
        return trim(preg_replace('#-+#', '-', str_replace('.', '-', File::getValidFilename($path))), '-');
    }

    public static function toCacheKey(string $text): string
    {
        return str_replace(['-', '~'], '_', self::webalize($text));
    }

    public static function toObjectKey(string $text): string
    {
        return preg_replace('#-+#', '-', str_replace(['_', '~'], '-', self::webalize($text)));
    }

    public static function cutStringRespectingWhitespace(string $string, int $length, string $suffix = '...'): string
    {
        if ($length < strlen($string)) {
            $text = substr($string, 0, $length);
            $string = (false !== ($length = strrpos($text, ' '))) ? substr($text, 0, $length) . $suffix : $text . $suffix;
        }
        return $string;
    }

    public static function mb_ucfirst(string $string, string $encoding = 'utf-8'): string
    {
        $strlen = mb_strlen($string, $encoding);
        return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding) . mb_substr($string, 1, $strlen - 1, $encoding);
    }

    public static function mb_lcfirst(string $string, string $encoding = 'utf-8'): string
    {
        $strlen = mb_strlen($string, $encoding);
        return mb_strtolower(mb_substr($string, 0, 1, $encoding), $encoding) . mb_substr($string, 1, $strlen - 1, $encoding);
    }

    public static function czechMonth(int $month): string
    {
        static $arr = [1 => 'leden', 'únor', 'březen', 'duben', 'květen', 'červen', 'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec'];
        return $arr[$month];
    }

    public static function czechDay(int $day): string
    {
        static $days = ['neděle', 'pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota'];
        return $days[$day];
    }

    public static function formatSizeUnits(int $size): string
    {
        if ($size >= 1073741824) {
            return number_format($size / 1073741824, 2, ',', ' ') . ' GB';
        } elseif ($size >= 1048576) {
            return number_format($size / 1048576, 2, ',', ' ') . ' MB';
        } elseif ($size >= 1024) {
            return number_format($size / 1024, 2, ',', ' ') . ' KB';
        } elseif ($size > 1) {
            return $size . ' B';
        } elseif ($size == 1) {
            return $size . ' B';
        } else {
            return '0 B';
        }
    }

    public static function getFileExtension(string $filename): string
    {
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        return $extension && !str_contains($extension, '/') ? $extension : '';
    }

    public static function serialize($data): string
    {
        return base64_encode(serialize($data));
    }

    public static function unserialize(string $data)
    {
        return unserialize(base64_decode($data));
    }

    public static function detectCzechEncoding(string $s): string
    {
        if (preg_match('#[\x80-\x{1FF}\x{2000}-\x{3FFF}]#u', $s)) {
            return 'UTF-8';
        } elseif (preg_match('#[\x7F-\x9F\xBC]#', $s)) {
            return 'WINDOWS-1250';
        } else {
            return 'ISO-8859-2';
        }
    }
}
