<?php

namespace App\Services\PromptLandingPageEditor;

use App\Models\PromptLandingPageAttachment;
use App\Services\PromptLandingPageHtmlSanitizer;

class SanitizeDocument
{
    public function __construct(private PromptLandingPageHtmlSanitizer $attachmentSanitizer) {}

    /**
     * @param  list<PromptLandingPageAttachment>  $attachments
     * @return array{html: string, css: string, js: string}
     */
    public function sanitize(string $html, string $css, string $js, array $attachments = []): array
    {
        [$html, $cssFromHtml] = $this->extractStyleTags($html);
        $html = $this->sanitizeHtml($html);
        $css = $this->sanitizeCss(trim($css."\n".$cssFromHtml));
        $js = $this->sanitizeJs($js);

        return [
            'html' => $this->attachmentSanitizer->sanitize($html, $attachments),
            'css' => $this->attachmentSanitizer->sanitize($css, $attachments),
            'js' => $this->attachmentSanitizer->sanitize($js, $attachments),
        ];
    }

    /**
     * @return array{0: string, 1: string}
     */
    private function extractStyleTags(string $html): array
    {
        $extraCss = '';
        $rewritten = preg_replace_callback(
            '#<style\b[^>]*>.*?</style>#is',
            function (array $matches) use (&$extraCss): string {
                if ($this->isEditorChromeCss($matches[0])) {
                    return '';
                }

                if (preg_match('#<style\b[^>]*>(.*?)</style>#is', $matches[0], $inner) === 1) {
                    $extraCss .= trim($inner[1])."\n";
                }

                return '';
            },
            $html,
        );

        return [is_string($rewritten) ? $rewritten : $html, $extraCss];
    }

    private function sanitizeHtml(string $html): string
    {
        $html = (string) preg_replace('#<script\b[^>]*>.*?</script>#is', '', $html);
        $html = (string) preg_replace('#<base\b[^>]*>#i', '', $html);
        $html = (string) preg_replace('#<meta\b[^>]*http-equiv=(["\']?)refresh\1[^>]*>#i', '', $html);
        $html = (string) preg_replace('/\sdata-gjs-[a-z0-9-]+="[^"]*"/i', '', $html);
        $html = (string) preg_replace("/\sdata-gjs-[a-z0-9-]+='[^']*'/i", '', $html);
        $html = (string) preg_replace('/\sdraggable="true"/i', '', $html);
        $html = $this->stripGrapesCssRuleNodes($html);

        $rewritten = preg_replace_callback(
            '/\b(href|src|action|formaction|xlink:href|poster)\s*=\s*(["\'])(.*?)\2/i',
            function (array $matches): string {
                $url = trim($matches[3]);

                if (preg_match('#^(javascript:|vbscript:|data:\s*text/html)#i', $url) === 1) {
                    return $matches[1].'='.$matches[2].'#'.$matches[2];
                }

                return $matches[0];
            },
            $html,
        );

        return is_string($rewritten) ? $rewritten : $html;
    }

    private function stripGrapesCssRuleNodes(string $html): string
    {
        $previous = null;

        while ($previous !== $html) {
            $previous = $html;
            $next = preg_replace('/<div[^>]*\bid=["\']gjs-css-rules[^"\']*["\'][^>]*>\s*<\/div>/i', '', $html);
            $html = is_string($next) ? $next : $html;
        }

        return $html;
    }

    private function sanitizeCss(string $css): string
    {
        $css = str_replace(['</style', '</STYLE'], ['<\\/style', '<\\/style'], $css);
        $css = (string) preg_replace('/expression\s*\(/i', 'invalid(', $css);
        $css = (string) preg_replace('/javascript\s*:/i', '', $css);
        $css = (string) preg_replace('/-moz-binding/i', 'invalid-binding', $css);
        $css = (string) preg_replace('/(?<![\w-])behavior\s*:/i', 'invalid:', $css);

        return $this->stripEditorChromeCss($css);
    }

    private function isEditorChromeCss(string $css): bool
    {
        return preg_match('/data-gjs-type|\.gjs-|::-webkit-scrollbar/i', $css) === 1;
    }

    private function stripEditorChromeCss(string $css): string
    {
        $css = (string) preg_replace('/\[data-gjs-type[^{]*\{[^{}]*\}/i', '', $css);
        $css = (string) preg_replace('/\.gjs-[A-Za-z0-9_-]*[^{]*\{[^{}]*\}/i', '', $css);

        return trim($css);
    }

    private function sanitizeJs(string $js): string
    {
        return str_replace(['</script', '</SCRIPT'], ['<\\/script', '<\\/script'], $js);
    }
}
