<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;

class TranslateHtmlStrings
{
    /**
     * @var array<string, array<string, string>>
     */
    private static array $translationMapCache = [];

    /**
     * @param  Closure(Request): Response  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        $locale = app()->getLocale();
        $fallbackLocale = (string) config('app.fallback_locale', 'en');

        if ($locale === '' || $locale === $fallbackLocale) {
            return $response;
        }

        if ($response instanceof StreamedResponse || $response instanceof BinaryFileResponse) {
            return $response;
        }

        $contentType = strtolower((string) $response->headers->get('Content-Type', ''));
        if (! str_contains($contentType, 'text/html')) {
            return $response;
        }

        $content = $response->getContent();
        if (! is_string($content) || $content === '') {
            return $response;
        }

        $translations = $this->translationMap($locale);
        if ($translations === []) {
            return $response;
        }

        $response->setContent($this->translateHtml($content, $translations));

        return $response;
    }

    /**
     * @return array<string, string>
     */
    private function translationMap(string $locale): array
    {
        if (array_key_exists($locale, self::$translationMapCache)) {
            return self::$translationMapCache[$locale];
        }

        $path = lang_path($locale.'.json');
        if (! is_readable($path)) {
            return self::$translationMapCache[$locale] = [];
        }

        $decoded = json_decode((string) file_get_contents($path), true);
        if (! is_array($decoded)) {
            return self::$translationMapCache[$locale] = [];
        }

        $map = [];
        foreach ($decoded as $source => $target) {
            if (! is_string($source) || ! is_string($target)) {
                continue;
            }

            $source = trim($source);
            if ($source === '' || $source === $target) {
                continue;
            }

            $map[$source] = $target;
        }

        // Apply longer phrases first to avoid partial replacements.
        uksort($map, static fn (string $a, string $b): int => mb_strlen($b) <=> mb_strlen($a));

        return self::$translationMapCache[$locale] = $map;
    }

    /**
     * @param  array<string, string>  $translations
     */
    private function translateHtml(string $html, array $translations): string
    {
        // Match full tags while allowing ">" inside quoted attributes (e.g. Alpine x-data).
        $segments = preg_split('/(<(?:[^"\'<>]|"[^"]*"|\'[^\']*\')+>)/', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
        if (! is_array($segments) || $segments === []) {
            return $html;
        }

        $inScript = false;
        $inStyle = false;

        foreach ($segments as $index => $segment) {
            $isTag = $index % 2 === 1;

            if ($isTag) {
                if (! $inScript && ! $inStyle) {
                    $segments[$index] = $this->translateTagAttributes($segment, $translations);
                }

                if (preg_match('/<script\b/i', $segment)) {
                    $inScript = true;
                }

                if (preg_match('/<\/script\s*>/i', $segment)) {
                    $inScript = false;
                }

                if (preg_match('/<style\b/i', $segment)) {
                    $inStyle = true;
                }

                if (preg_match('/<\/style\s*>/i', $segment)) {
                    $inStyle = false;
                }

                continue;
            }

            if (! $inScript && ! $inStyle) {
                $segments[$index] = strtr($segment, $translations);
            }
        }

        return implode('', $segments);
    }

    /**
     * @param  array<string, string>  $translations
     */
    private function translateTagAttributes(string $tag, array $translations): string
    {
        return (string) preg_replace_callback(
            '/\b(placeholder|title|aria-label|alt)=([\'"])(.*?)\2/iu',
            static function (array $matches) use ($translations): string {
                $name = $matches[1] ?? '';
                $quote = $matches[2] ?? '"';
                $value = $matches[3] ?? '';

                if (! is_string($name) || ! is_string($quote) || ! is_string($value) || $value === '') {
                    return $matches[0] ?? '';
                }

                $translated = strtr($value, $translations);

                return sprintf('%s=%s%s%s', $name, $quote, $translated, $quote);
            },
            $tag
        );
    }
}
