<?php

namespace App\Twig;

use Detection\MobileDetect;
use Nelmio\SecurityBundle\EventListener\ContentSecurityPolicyListener;
use Psr\Cache\CacheItemPoolInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

class ViteAssetExtension extends AbstractExtension
{
    public const CACHE_KEY = 'vite_manifest';
    /** @var array<string, array<string, mixed>>|null */
    private ?array $manifestData = null;
    private readonly bool $isDev;

    public function __construct(
        private readonly string $manifest,
        private readonly CacheItemPoolInterface $cache,
        private readonly ?ContentSecurityPolicyListener $cspListener = null,
    ) {
        $this->isDev = '0' === $_ENV['VITE_LOAD'];
    }

    /** @return list<TwigFunction> */
    public function getFunctions(): array
    {
        return [
            new TwigFunction('vite_asset', $this->asset(...), ['is_safe' => ['html']]),
            new TwigFunction('isMobile', $this->isMobile(...), ['is_safe' => ['html']]),
            new TwigFunction('vite_favicons', $this->favicons(...), ['is_safe' => ['html']]),
        ];
    }

    protected function getNonce(): string
    {
        return $this->cspListener?->getNonce('script') ?? '';
    }

    public function isMobile(): bool
    {
        $detect = new MobileDetect();

        return $detect->isMobile() || $detect->isTablet();
    }

    private function loadManifest(): void
    {
        if (null === $this->manifestData) {
            $item = $this->cache->getItem(self::CACHE_KEY);
            if ($item->isHit()) {
                $this->manifestData = $item->get();
            } else {
                if (!file_exists($this->manifest)) {
                    $this->manifestData = [];

                    return;
                }
                $this->manifestData = json_decode((string) file_get_contents($this->manifest), true);
                $item->set($this->manifestData);
                $this->cache->save($item);
            }
        }
    }

    /**
     * @param list<string> $deps
     */
    public function asset(string $entry, array $deps = []): string
    {
        return $this->isDev ? $this->assetDev($entry, $deps) : $this->assetProd($entry);
    }

    /**
     * @param list<string> $deps
     */
    public function assetDev(string $entry, array $deps): string
    {
        $nonce = $this->getNonce();

        return <<<HTML
            <script type="module" src="http://localhost:5173/assets/@vite/client" nonce="{$nonce}"></script>
            <script type="module" src="http://localhost:5173/assets/{$entry}" nonce="{$nonce}" defer></script>
HTML;
    }

    public function assetProd(string $entry): string
    {
        $this->loadManifest();
        $nonce = $this->getNonce();

        $file = $this->manifestData[$entry]['file'] ?? '';
        $css = $this->manifestData[$entry]['css'] ?? [];

        $html = <<<HTML
            <script type="module" src="/build/{$file}" crossorigin="anonymous" nonce="{$nonce}" defer></script>
HTML;

        foreach ($css as $cssFile) {
            $html .= '<link rel="stylesheet" href="/build/'.$cssFile.'" crossorigin="anonymous"/>';
        }

        return $html;
    }

    public function favicons(): string
    {
        return $this->isDev ? '<link rel="icon" href="/favicon.ico">' : $this->faviconsProd();
    }

    private function faviconsProd(): string
    {
        $this->loadManifest();
        $faviconHtml = '';
        foreach ($this->manifestData as $key => $favicon) {
            if (!str_contains($key, '.js') && isset($favicon['file'])) {
                $faviconHtml .= '<link rel="icon" href="/build/'.$favicon['file'].'" type="image/x-icon">';
            }
        }

        return $faviconHtml;
    }
}
