<?php

namespace App\Services;

use App\Models\Page;
use Illuminate\Support\Facades\Log;

class AiStoreWebsiteBuilderService
{
    private DeepSeekService $deepSeek;

    public function __construct(DeepSeekService $deepSeek)
    {
        $this->deepSeek = $deepSeek;
    }

    public function generateStoreWebsiteHtml(Page $page, string $prompt = '', array $conversation = [], string $currentHtml = ''): string
    {
        $products = \App\Models\BusinessProducts::query()
            ->where('business_id', (int) $page->id)
            ->where('status', 1)
            ->orderBy('id', 'desc')
            ->get(['id', 'product_name', 'product_description', 'price', 'currency', 'image1', 'image2', 'image3', 'image4']);

        $storeData = [
            'name' => (string) $page->title,
            'description' => (string) $page->description,
            'email' => (string) $page->business_email,
            'contact' => (string) $page->business_contact,
            'address' => (string) $page->full_address,
            'logo' => (string) $page->logo,
            'theme2_logo' => (string) ($page->theme2_logo ?? ''),
            'slug' => (string) $page->slug,
            'products_count' => $products->count(),
            'products_sample' => $products->take(12)->map(function ($p) {
                return [
                    'id' => (int) $p->id,
                    'product_name' => (string) ($p->product_name ?? ''),
                    'product_description' => (string) ($p->product_description ?? ''),
                    'price' => $p->price,
                    'currency' => (string) ($p->currency ?? ''),
                    'image1' => (string) ($p->image1 ?? ''),
                    'image2' => (string) ($p->image2 ?? ''),
                    'image3' => (string) ($p->image3 ?? ''),
                    'image4' => (string) ($p->image4 ?? ''),
                ];
            })->values(),
        ];

        $system = implode("\n", [
            'You generate a single HTML storefront page.',
            'Return ONLY HTML. No markdown. No backticks.',
            'Use only vanilla HTML/CSS/JS.',
            'Make it production-ready and responsive.',
            'Design quality must be premium, visually rich, and modern (not plain).',
            'Use strong typography, clear visual hierarchy, meaningful spacing, and polished card/UI styling.',
            'Use CSS variables and an intentional color system, not default gray blocks.',
            'Avoid raw placeholder-looking layout; no giant empty boxes.',
            'Add subtle but meaningful motion/hover interactions where appropriate.',
            'Include clear sections: hero, featured products grid, and CTA/details area.',
            'It MUST fetch live store data from window.AI_STORE_DATA_URL and render products dynamically.',
            'STRICT REQUIREMENT: include exact token id="ai-store-root" in the HTML.',
            'STRICT REQUIREMENT: include exact token window.AI_STORE_DATA_URL in JavaScript.',
            'STRICT REQUIREMENT: include fetch(window.AI_STORE_DATA_URL...) and render into id="ai-store-root".',
            'STRICT REQUIREMENT: render business logo in header/nav and bind it from store.theme2_logo or store.logo from fetched data.',
            'STRICT REQUIREMENT: always include a product section in first response and all subsequent responses; never remove or disable it.',
            'STRICT REQUIREMENT: products must come from fetched data; never hardcode or randomize product names, prices, descriptions, stock, or images.',
            'Product fields from API may be either product_name/product_description or name/title/description/body. Support both key styles.',
            'Show complete store and product information available from API, including metadata fields when present.',
            'For each product, display rich details: name, description, price, currency, media, stock/SKU/discount/video when available.',
            'Use premium decorative treatment: layered gradients, glass surfaces, accent borders, depth shadows, and smooth reveal animations.',
            'NEVER render raw HTML source code as visible page content.',
            'Keep script inline.',
        ]);

        $conversationContext = $this->buildConversationContext($conversation);
        $currentHtml = $this->normalizeGeneratedHtml($currentHtml);
        $effectivePrompt = trim($prompt);
        $isUpdateRequest = $currentHtml !== '';
        $currentHtml = $this->trimHtmlContextForPrompt($currentHtml, $isUpdateRequest ? 42000 : 18000);
        $persistentDirectives = $this->buildPersistentUserDirectives($effectivePrompt, $conversation, $currentHtml);
        if ($effectivePrompt === '' && $isUpdateRequest) {
            $effectivePrompt = 'Please improve the current storefront design and apply the latest edit request context.';
        }

        $userPrompt = "Build or update a modern storefront."
            . "\nStore summary: " . json_encode($storeData)
            . "\nLatest user request: " . $effectivePrompt
            . "\nPersistent user-requested changes to keep in this response:\n" . $persistentDirectives
            . "\nRecent conversation:\n" . $conversationContext
            . ($isUpdateRequest
                ? "\n\nIMPORTANT UPDATE MODE:\n- You MUST modify the provided current HTML, not rebuild from scratch.\n- Keep all previous edits unless the latest request explicitly changes them.\n- Preserve existing sections, wording, product rendering logic, and logo changes unless user asks otherwise.\n- You MUST apply the latest user request and reflect those changes in returned HTML.\n\nCurrent HTML to modify:\n" . $currentHtml
                : '');

        Log::info('AI storefront request prepared.', [
            'page_id' => (int) ($page->id ?? 0),
            'slug' => (string) ($page->slug ?? ''),
            'is_update_request' => (bool) $isUpdateRequest,
            'prompt_length' => strlen((string) $effectivePrompt),
            'conversation_count' => count($conversation),
            'current_html_length' => strlen((string) $currentHtml),
            'products_count' => (int) $products->count(),
        ]);

        $initialMaxTokens = $this->adaptiveInitialGenerationMaxTokens(
            (bool) $isUpdateRequest,
            strlen((string) $currentHtml),
            strlen((string) $effectivePrompt)
        );

        try {
            $result = $this->deepSeek->chat([
                ['role' => 'system', 'content' => $system],
                ['role' => 'user', 'content' => $userPrompt],
            ], [
                'temperature' => 0.35,
                'max_tokens' => $initialMaxTokens,
                'timeout' => 120,
                'connect_timeout' => 12,
                'retry_attempts' => 2,
                'request_reasoning' => false,
            ]);
        } catch (\Throwable $e) {
            Log::error('AI store website generation request failed.', [
                'page_id' => (int) ($page->id ?? 0),
                'slug' => (string) ($page->slug ?? ''),
                'prompt_preview' => function_exists('mb_substr') ? mb_substr($effectivePrompt, 0, 250) : substr($effectivePrompt, 0, 250),
                'is_update_request' => (bool) $isUpdateRequest,
                'conversation_count' => count($conversation),
                'exception' => $e,
            ]);
            throw new \RuntimeException('AI generation failed: ' . $e->getMessage());
        }

        $rawContent = (string) ($result['content'] ?? '');
        $finishReason = $this->extractFinishReason($result);
        if ($finishReason === 'length') {
            $rawContent = $this->completeTruncatedHtmlResponse(
                $system,
                $userPrompt,
                $rawContent,
                'DeepSeek response truncated by token limit; requesting continuation.',
                [
                    'page_id' => (int) ($page->id ?? 0),
                    'slug' => (string) ($page->slug ?? ''),
                ]
            );
        }

        $html = $this->normalizeGeneratedHtml($rawContent);
        if ($html === '') {
            throw new \RuntimeException('AI returned empty HTML.');
        }

        Log::info('AI storefront response received.', [
            'page_id' => (int) ($page->id ?? 0),
            'slug' => (string) ($page->slug ?? ''),
            'html_length' => strlen((string) $html),
            'has_root' => (bool) preg_match('/id\s*=\s*["\']ai-store-root["\']/i', $html),
            'has_data_url' => (bool) preg_match('/window\s*\.\s*AI_STORE_DATA_URL/i', $html),
            'has_fetch' => (bool) preg_match('/fetch\s*\(\s*(window\s*\.\s*AI_STORE_DATA_URL|window\s*\[\s*["\']AI_STORE_DATA_URL["\']\s*\])/i', $html),
        ]);

        $html = $this->ensureFullHtmlDocument($html);
        $html = $this->injectDataUrlVar($html, (string) $page->slug);
        $html = $this->ensureValidRenderableHtmlOrFallback($html, (string) $page->slug);

        // Keep generation to a single AI call to avoid long request timeouts on production gateways.

        if (! $this->hasDynamicStoreBinding($html)) {
            $html = $this->enforceDynamicStoreBinding($html, (string) $page->slug);
            if (! $this->hasDynamicStoreBinding($html)) {
                throw new \RuntimeException('AI HTML missing dynamic binding (ai-store-root + AI_STORE_DATA_URL + fetch). Please regenerate.');
            }
        }

        $html = $this->ensureRenderableOutput($page, $html);
        $html = $this->applyPromptVisualOverrides($html, $effectivePrompt, $conversation);

        return $html;
    }

    private function applyPromptVisualOverrides(string $html, string $prompt, array $conversation = []): string
    {
        if ($this->wantsRedProductButtons($prompt, $conversation)) {
            $html = $this->injectRedProductButtonOverride($html);
        }

        return $html;
    }

    private function wantsRedProductButtons(string $prompt, array $conversation = []): bool
    {
        $parts = [strtolower((string) $prompt)];
        foreach ($conversation as $item) {
            if (is_array($item)) {
                $parts[] = strtolower((string) ($item['content'] ?? ''));
                continue;
            }
            $parts[] = strtolower((string) $item);
        }

        $haystack = implode("\n", array_filter($parts, static fn ($v) => $v !== ''));
        if ($haystack === '') {
            return false;
        }

        $mentionsButton = (bool) preg_match('/(view\s*product|buy\s*now|product\s*button|button\s*color|cta\s*button)/i', $haystack);
        $mentionsRed = (bool) preg_match('/\bred\b|#e63946|#ff4d5a|danger\s*button/i', $haystack);

        return $mentionsButton && $mentionsRed;
    }

    private function injectRedProductButtonOverride(string $html): string
    {
        $marker = 'ai-store-red-product-button-override';
        if (strpos($html, $marker) !== false) {
            return $html;
        }

        $inject = <<<'HTML'
<style id="ai-store-red-product-button-override">
.view-product-btn,
.ai-rescue-link,
.ai-product-safety-link,
.ai-premium-buy-now,
#ai-store-root a[href*="/product/"] {
    background: #e63946 !important;
    border-color: #e63946 !important;
    color: #ffffff !important;
    box-shadow: 0 8px 24px rgba(230, 57, 70, 0.28) !important;
}
.view-product-btn:hover,
.ai-rescue-link:hover,
.ai-product-safety-link:hover,
.ai-premium-buy-now:hover,
#ai-store-root a[href*="/product/"]:hover {
    background: #ff4d5a !important;
    border-color: #ff4d5a !important;
    color: #ffffff !important;
}
</style>
<script id="ai-store-red-product-button-override-script">
(function(){
    var selectors = [
        '.view-product-btn',
        '.ai-rescue-link',
        '.ai-product-safety-link',
        '.ai-premium-buy-now',
        '#ai-store-root a[href*="/product/"]'
    ];
    var nodes = document.querySelectorAll(selectors.join(','));
    for (var i = 0; i < nodes.length; i++) {
        var el = nodes[i];
        var txt = String(el.textContent || '').toLowerCase().trim();
        if (txt === 'click to buy' || txt === 'buy now') {
            el.textContent = 'View product';
        }
    }
})();
</script>
HTML;

        if (stripos($html, '</head>') !== false) {
            return str_ireplace('</head>', $inject . "\n</head>", $html);
        }
        if (stripos($html, '</body>') !== false) {
            return str_ireplace('</body>', $inject . "\n</body>", $html);
        }

        return $html . "\n" . $inject;
    }

    public function deployPageHtml(Page $page, string $html): array
    {
        $html = $this->normalizeGeneratedHtml($html);
        if ($html === '') {
            throw new \RuntimeException('Generated HTML is empty.');
        }

        $html = $this->ensureFullHtmlDocument($html);
        $html = $this->injectDataUrlVar($html, (string) $page->slug);
        $html = $this->ensureValidRenderableHtmlOrFallback($html, (string) $page->slug);

        if (! $this->hasDynamicStoreBinding($html)) {
            $html = $this->enforceDynamicStoreBinding($html, (string) $page->slug);
            if (! $this->hasDynamicStoreBinding($html)) {
                throw new \RuntimeException('Cannot deploy: AI HTML missing dynamic binding (ai-store-root + AI_STORE_DATA_URL + fetch).');
            }
        }
        $html = $this->ensureRenderableOutput($page, $html);

        if (\Schema::hasColumn('pages', 'html_edit_code_theme2')) {
            $page->html_edit_code_theme2 = $html;
        }
        if (\Schema::hasColumn('pages', 'ai_html_code')) {
            $page->ai_html_code = $html;
        }
        if (\Schema::hasColumn('pages', 'html_code')) {
            $page->html_code = $this->extractBodyHtml($html);
        }
        if (\Schema::hasColumn('pages', 'theme')) {
            $page->theme = 4;
        }
        if (\Schema::hasColumn('pages', 'ai_website_deploy')) {
            $page->ai_website_deploy = 1;
        }
        $page->save();

        $deployDir = public_path('store/' . (int) $page->id);
        if (! is_dir($deployDir) && ! @mkdir($deployDir, 0775, true) && ! is_dir($deployDir)) {
            throw new \RuntimeException('Failed to create deploy directory for store page.');
        }

        $filePath = $deployDir . DIRECTORY_SEPARATOR . 'index.html';
        if (@file_put_contents($filePath, $html) === false) {
            throw new \RuntimeException('Failed to write deployed store HTML file.');
        }

        return [
            'path' => $filePath,
            'url' => \URL::to('/store/' . $page->slug),
        ];
    }

    public function ensureRenderableOutput(Page $page, string $html): string
    {
        $slug = (string) ($page->slug ?? '');

        $html = $this->normalizeGeneratedHtml($html);
        if ($html === '') {
            throw new \RuntimeException('AI generated empty HTML after normalization.');
        }

        if ($this->looksTruncatedHtml($html)) {
            // Keep this path silent: attempt a best-effort repair without warning/error logs.
            $html = $this->repairLikelyTruncatedHtml($html);
        }

        if ($this->containsCriticalTemplateArtifacts($html) || $this->containsUnrenderedTemplateArtifacts($html)) {
            Log::warning('Sanitizer detected template artifacts; continuing with rescue sanitization path.', [
                'slug' => $slug,
                'html_length' => strlen((string) $html),
            ]);
        }

        $html = $this->ensureFullHtmlDocument($html);
        $html = $this->injectDataUrlVar($html, $slug);
        $html = $this->repairInlineScriptStringLiterals($html);
        $html = $this->stripLikelyBrokenInlineScripts($html);
        $html = $this->quarantineBrokenInlineJsScripts($html, $slug);

        if ($this->hasLikelyBrokenInlineJsSyntax($html)) {
            Log::warning('Detected likely broken inline JS syntax; using runtime rescue path (AI syntax repair skipped).', [
                'slug' => $slug,
                'html_length' => strlen((string) $html),
            ]);
        }

        if (! $this->hasDynamicStoreBinding($html)) {
            $html = $this->enforceDynamicStoreBinding($html, $slug);
            if (! $this->hasDynamicStoreBinding($html)) {
                throw new \RuntimeException('AI HTML is missing required dynamic store binding.');
            }
        }

        // Add a resilient runtime renderer so dynamic store data still appears
        // if any model-generated inline script fails at runtime.
        $html = $this->injectRuntimeRescueRendererScript($html);
        $html = $this->injectProductHydrationSafetyScript($html);

        Log::info('AI storefront HTML accepted.', [
            'slug' => $slug,
            'html_length' => strlen((string) $html),
            'has_dynamic_binding' => $this->hasDynamicStoreBinding($html),
        ]);

        return $html;
    }

    private function repairInlineScriptStringLiterals(string $html): string
    {
        if (stripos($html, '<script') === false) {
            return $html;
        }

        $fixedScripts = 0;
        $totalReplacements = 0;

        $out = preg_replace_callback(
            '#<script\b([^>]*)>([\s\S]*?)</script>#i',
            function (array $m) use (&$fixedScripts, &$totalReplacements) {
                $attrs = (string) ($m[1] ?? '');
                $code = (string) ($m[2] ?? '');

                // Keep external scripts untouched.
                if (preg_match('/\bsrc\s*=\s*["\"]/i', $attrs)) {
                    return $m[0];
                }

                // Only process JavaScript script blocks.
                if (preg_match('/\btype\s*=\s*["\"]([^"\"]+)["\"]/i', $attrs, $tm)) {
                    $type = strtolower(trim((string) ($tm[1] ?? '')));
                    if ($type !== '' && $type !== 'text/javascript' && $type !== 'application/javascript' && $type !== 'module') {
                        return $m[0];
                    }
                }

                [$fixedCode, $replacements] = $this->escapeUnescapedJsStringNewlines($code);
                if ($replacements > 0) {
                    $fixedScripts++;
                    $totalReplacements += $replacements;
                }

                return '<script' . $attrs . '>' . $fixedCode . '</script>';
            },
            $html
        );

        if (! is_string($out)) {
            return $html;
        }

        if ($fixedScripts > 0) {
            Log::info('Repaired inline AI script string newlines.', [
                'scripts_repaired' => $fixedScripts,
                'newline_replacements' => $totalReplacements,
            ]);
        }

        return $out;
    }

    private function escapeUnescapedJsStringNewlines(string $code): array
    {
        $len = strlen($code);
        if ($len === 0) {
            return [$code, 0];
        }

        $out = '';
        $inSingle = false;
        $inDouble = false;
        $inTemplate = false;
        $escaped = false;
        $replacements = 0;

        for ($i = 0; $i < $len; $i++) {
            $ch = $code[$i];

            if ($ch === "\r" || $ch === "\n") {
                if (($inSingle || $inDouble) && ! $escaped) {
                    $out .= '\\n';
                    $replacements++;

                    // Preserve CRLF as one newline replacement.
                    if ($ch === "\r" && $i + 1 < $len && $code[$i + 1] === "\n") {
                        $i++;
                    }

                    $escaped = false;
                    continue;
                }

                $out .= $ch;
                $escaped = false;
                continue;
            }

            if (! $inSingle && ! $inDouble && ! $inTemplate) {
                if ($ch === "'") {
                    $inSingle = true;
                    $out .= $ch;
                    $escaped = false;
                    continue;
                }
                if ($ch === '"') {
                    $inDouble = true;
                    $out .= $ch;
                    $escaped = false;
                    continue;
                }
                if ($ch === '`') {
                    $inTemplate = true;
                    $out .= $ch;
                    $escaped = false;
                    continue;
                }
            } elseif ($inSingle) {
                if ($ch === "'" && ! $escaped) {
                    $inSingle = false;
                }
            } elseif ($inDouble) {
                if ($ch === '"' && ! $escaped) {
                    $inDouble = false;
                }
            } elseif ($inTemplate) {
                if ($ch === '`' && ! $escaped) {
                    $inTemplate = false;
                }
            }

            $out .= $ch;

            if ($ch === '\\') {
                $escaped = ! $escaped;
            } else {
                $escaped = false;
            }
        }

        return [$out, $replacements];
    }

    private function hasLikelyBrokenInlineJsSyntax(string $html): bool
    {
        if (stripos($html, '<script') === false) {
            return false;
        }

        if (! preg_match_all('#<script\b([^>]*)>([\s\S]*?)</script>#i', $html, $matches, PREG_SET_ORDER)) {
            return false;
        }

        foreach ($matches as $m) {
            $attrs = (string) ($m[1] ?? '');
            $code = (string) ($m[2] ?? '');

            if (preg_match('/\bsrc\s*=\s*["\"]/i', $attrs)) {
                continue;
            }

            if (preg_match('/\btype\s*=\s*["\"]([^"\"]+)["\"]/i', $attrs, $tm)) {
                $type = strtolower(trim((string) ($tm[1] ?? '')));
                if ($type !== '' && $type !== 'text/javascript' && $type !== 'application/javascript' && $type !== 'module') {
                    continue;
                }
            }

            if ($this->isLikelyBrokenJsBlock($code)) {
                return true;
            }
        }

        return false;
    }

    private function isLikelyBrokenJsBlock(string $code): bool
    {
        $len = strlen($code);
        if ($len === 0) {
            return false;
        }

        $curly = 0;
        $paren = 0;
        $square = 0;

        $inSingle = false;
        $inDouble = false;
        $inTemplate = false;
        $inLineComment = false;
        $inBlockComment = false;
        $escaped = false;

        for ($i = 0; $i < $len; $i++) {
            $ch = $code[$i];
            $next = ($i + 1 < $len) ? $code[$i + 1] : '';

            if ($inLineComment) {
                if ($ch === "\n") {
                    $inLineComment = false;
                }
                continue;
            }

            if ($inBlockComment) {
                if ($ch === '*' && $next === '/') {
                    $inBlockComment = false;
                    $i++;
                }
                continue;
            }

            if (! $inSingle && ! $inDouble && ! $inTemplate) {
                if ($ch === '/' && $next === '/') {
                    $inLineComment = true;
                    $i++;
                    continue;
                }
                if ($ch === '/' && $next === '*') {
                    $inBlockComment = true;
                    $i++;
                    continue;
                }
            }

            if (! $inDouble && ! $inTemplate && $ch === "'" && ! $escaped) {
                $inSingle = ! $inSingle;
                continue;
            }
            if (! $inSingle && ! $inTemplate && $ch === '"' && ! $escaped) {
                $inDouble = ! $inDouble;
                continue;
            }
            if (! $inSingle && ! $inDouble && $ch === '`' && ! $escaped) {
                $inTemplate = ! $inTemplate;
                continue;
            }

            if ($inSingle || $inDouble || $inTemplate) {
                if ($ch === '\\') {
                    $escaped = ! $escaped;
                } else {
                    $escaped = false;
                }
                continue;
            }

            if ($ch === '{') {
                $curly++;
            } elseif ($ch === '}') {
                $curly--;
            } elseif ($ch === '(') {
                $paren++;
            } elseif ($ch === ')') {
                $paren--;
            } elseif ($ch === '[') {
                $square++;
            } elseif ($ch === ']') {
                $square--;
            }

            if ($curly < 0 || $paren < 0 || $square < 0) {
                return true;
            }
        }

        return $inSingle || $inDouble || $inTemplate || $inBlockComment || $curly !== 0 || $paren !== 0 || $square !== 0;
    }

    private function quarantineBrokenInlineJsScripts(string $html, string $slug = ''): string
    {
        if (stripos($html, '<script') === false) {
            return $html;
        }

        $quarantined = 0;
        $out = preg_replace_callback(
            '#<script\b([^>]*)>([\s\S]*?)</script>#i',
            function (array $m) use (&$quarantined) {
                $attrs = (string) ($m[1] ?? '');
                $code = (string) ($m[2] ?? '');

                if (preg_match('/\bsrc\s*=\s*["\"]/i', $attrs)) {
                    return $m[0];
                }

                if (preg_match('/\btype\s*=\s*["\"]([^"\"]+)["\"]/i', $attrs, $tm)) {
                    $type = strtolower(trim((string) ($tm[1] ?? '')));
                    if ($type !== '' && $type !== 'text/javascript' && $type !== 'application/javascript' && $type !== 'module') {
                        return $m[0];
                    }
                }

                if (! $this->isLikelyBrokenJsBlock($code)) {
                    return $m[0];
                }

                $quarantined++;

                return '<script type="application/x-ai-broken-javascript" data-ai-quarantined="1">' . $code . '</script>';
            },
            $html
        );

        if (! is_string($out)) {
            return $html;
        }

        if ($quarantined > 0) {
            Log::info('Quarantined broken inline AI scripts to keep storefront renderable.', [
                'slug' => $slug,
                'quarantined_scripts' => $quarantined,
            ]);
        }

        return $out;
    }

    private function repairInlineJsSyntaxWithAi(string $html, string $slug): string
    {
        $htmlForRepair = $this->trimHtmlContextForPrompt($html, 18000);

        $system = implode("\n", [
            'You repair JavaScript syntax errors inside an HTML storefront document.',
            'Return ONLY corrected full HTML. No markdown. No backticks. No explanation.',
            'Do not remove script logic unless absolutely necessary for syntax validity.',
            'Preserve design and behavior.',
            'Keep required tokens exactly: id="ai-store-root", window.AI_STORE_DATA_URL, and fetch(window.AI_STORE_DATA_URL...).',
        ]);

        $user = "Fix JavaScript syntax errors in this HTML while preserving behavior and layout."
            . "\nStore slug: " . $slug
            . "\n\nHTML:\n" . $htmlForRepair;

        try {
            $result = $this->deepSeek->chat([
                ['role' => 'system', 'content' => $system],
                ['role' => 'user', 'content' => $user],
            ], [
                'temperature' => 0.1,
                'max_tokens' => 5500,
                'timeout' => 120,
                'connect_timeout' => 12,
                'retry_attempts' => 2,
                'request_reasoning' => false,
            ]);

            $rawContent = (string) ($result['content'] ?? '');
            $finishReason = $this->extractFinishReason($result);
            if ($finishReason === 'length') {
                $rawContent = $this->completeTruncatedHtmlResponse(
                    $system,
                    $user,
                    $rawContent,
                    'AI syntax repair response truncated; requesting continuation.',
                    [
                        'slug' => $slug,
                    ]
                );
            }

            $fixed = $this->normalizeGeneratedHtml($rawContent);

            return $fixed;
        } catch (\Throwable $e) {
            Log::warning('AI inline JS syntax repair failed.', [
                'slug' => $slug,
                'exception' => $e,
            ]);

            return '';
        }
    }

    private function stripLikelyBrokenInlineScripts(string $html): string
    {
        if (stripos($html, '<script') === false) {
            return $html;
        }

        $removed = 0;
        $removedExternal = 0;
        $out = preg_replace_callback(
            '#<script\b([^>]*)>([\s\S]*?)</script>#i',
            function (array $m) use (&$removed, &$removedExternal) {
                $attrs = (string) ($m[1] ?? '');
                $code = (string) ($m[2] ?? '');

                // Keep external scripts.
                if (preg_match('/\bsrc\s*=\s*["\"]/i', $attrs)) {
                    $src = '';
                    if (preg_match('/\bsrc\s*=\s*["\']([^"\']+)["\']/i', $attrs, $sm)) {
                        $src = html_entity_decode((string) ($sm[1] ?? ''), ENT_QUOTES | ENT_HTML5, 'UTF-8');
                    }

                    $isSuspiciousSrc = false;
                    if ($src !== '') {
                        $lowerSrc = strtolower(trim($src));
                        if ($lowerSrc !== '') {
                            $isSuspiciousSrc = (bool) preg_match('#(^data:|[a-z]:[\\/]|\.tmp(?:$|[?#])|/tmp/|php[0-9a-z]+\.tmp)#i', $lowerSrc);
                        }
                    }

                    if ($isSuspiciousSrc) {
                        $removedExternal++;

                        return '';
                    }

                    return $m[0];
                }

                // Keep non-JS script payloads (json/template data blocks).
                if (preg_match('/\btype\s*=\s*["\"]([^"\"]+)["\"]/i', $attrs, $tm)) {
                    $type = strtolower(trim((string) ($tm[1] ?? '')));
                    if ($type !== '' && $type !== 'text/javascript' && $type !== 'application/javascript' && $type !== 'module') {
                        return $m[0];
                    }
                }

                // Keep only known-safe system scripts that we inject ourselves.
                $isSafeSystemScript = (strpos($code, 'window.__AI_STORE_RUNTIME_RESCUE__') !== false)
                    || (strpos($code, 'window.__AI_STORE_LOGO_HYDRATE__') !== false);

                if ($isSafeSystemScript) {
                    return $m[0];
                }

                // Remove model JS even when it "looks valid" to prevent runtime parser errors.
                // This storefront path uses enforced/rescue scripts for dynamic behavior.
                if (! $this->looksLikeBrokenInlineJs($code) && trim($code) === '') {
                    return $m[0];
                }

                $removed++;

                return '';
            },
            $html
        );

        if (! is_string($out)) {
            return $html;
        }

        if ($removed > 0) {
            Log::info('Removed untrusted inline AI scripts from storefront HTML.', [
                'removed_scripts' => $removed,
            ]);
        }

        if ($removedExternal > 0) {
            Log::info('Removed suspicious external script sources from storefront HTML.', [
                'removed_external_scripts' => $removedExternal,
            ]);
        }

        return $out;
    }

    private function looksLikeBrokenInlineJs(string $code): bool
    {
        $js = trim($code);
        if ($js === '') {
            return false;
        }

        // Typical broken model output fragments that later trigger runtime parser errors.
        $artifactPatterns = [
            '/\+\s*esc\s*\(/i',
            '/esc\s*\([^\)]*\)\s*\+/i',
            '/["\']\s*\+\s*[a-zA-Z_$][\w$]*\s*\+/i',
            '/logo\s*\?\s*\(\s*["\']Store\s+logo["\']\s*\)\s*:\s*["\']\s*["\']\s*\)\s*\+/i',
        ];
        foreach ($artifactPatterns as $p) {
            if (preg_match($p, $js)) {
                return true;
            }
        }

        // Heuristic: clearly unmatched quote counts (ignoring escaped quotes).
        $single = preg_match_all("/(?<!\\\\)'/", $js, $m1);
        $double = preg_match_all('/(?<!\\\\)"/', $js, $m2);
        if ((int) $single % 2 !== 0 || (int) $double % 2 !== 0) {
            return true;
        }

        return false;
    }

    public function generateProductImageVariants(string $imageDataUrl, string $prompt = '', int $count = 4): array
    {
        $siteEnabled = $this->configSiteValue('enable-google-ai-store-images', 'site.google-ai-store.enable-google-ai-store-images.value', '1');
        $siteEnabled = is_string($siteEnabled) ? strtolower(trim($siteEnabled)) : (string) $siteEnabled;
        if (in_array($siteEnabled, ['0', 'false', 'no', 'off'], true)) {
            throw new \RuntimeException('Google AI image generation is disabled in site settings.');
        }

        $siteApiKey = (string) $this->configSiteValue('google-ai-store-gemini-api-key', 'site.google-ai-store.google-ai-store-gemini-api-key.value', '');
        $siteModel = (string) $this->configSiteValue('google-ai-store-gemini-image-model', 'site.google-ai-store.google-ai-store-gemini-image-model.value', '');
        $siteTimeout = (int) $this->configSiteValue('google-ai-store-timeout-seconds', 'site.google-ai-store.google-ai-store-timeout-seconds.value', 120);

        $apiKey = $siteApiKey !== '' ? $siteApiKey : (string) config('services.gemini.key');
        $model = $siteModel !== '' ? $siteModel : (string) config('services.gemini.image_model', 'gemini-2.5-flash-image');
        $model = $this->normalizeGeminiModelName($model);

        if ($apiKey === '') {
            throw new \RuntimeException('Gemini API key is not configured. Set GEMINI_API_KEY.');
        }

        if (! preg_match('#^data:(image/[a-zA-Z0-9.+-]+);base64,(.+)$#', $imageDataUrl, $m)) {
            throw new \RuntimeException('Invalid source image format.');
        }

        $mime = (string) $m[1];
        $base64 = (string) $m[2];

        $instruction = trim($prompt);
        if ($instruction === '') {
            $instruction = 'Create 4 clean ecommerce-ready product variations from this source image with studio lighting and white background.';
        }

        $basePayload = [
            'contents' => [[
                'parts' => [
                    ['text' => $instruction . ' Return image outputs only.'],
                    ['inline_data' => ['mime_type' => $mime, 'data' => $base64]],
                ],
            ]],
            'generationConfig' => [
                'responseModalities' => ['TEXT', 'IMAGE'],
            ],
        ];

        $candidateModels = array_values(array_unique(array_filter([
            trim((string) $model),
            'gemini-2.5-flash-image',
            'gemini-3.1-flash-image-preview',
            'gemini-3.1-flash-image',
            'gemini-3-pro-image-preview',
            'gemini-3-pro-image',
            'nano-banana-pro-preview',
        ])));

        $lastError = '';
        $targetCount = max(1, (int) $count);
        $collected = [];
        $seen = [];

        $addUnique = function (array $images) use (&$collected, &$seen, $targetCount): void {
            foreach ($images as $img) {
                $img = (string) $img;
                if ($img === '') {
                    continue;
                }

                $hash = md5($img);
                if (isset($seen[$hash])) {
                    continue;
                }

                $seen[$hash] = true;
                $collected[] = $img;

                if (count($collected) >= $targetCount) {
                    return;
                }
            }
        };

        foreach ($candidateModels as $candidateModel) {
            $attempts = min(6, max(2, $targetCount * 2));
            for ($attempt = 0; $attempt < $attempts; $attempt++) {
                $payload = $basePayload;
                if ($attempt > 0) {
                    $payload['contents'][0]['parts'][0]['text'] = $instruction
                        . ' Create a distinct variation number ' . ($attempt + 1)
                        . ' of ' . $targetCount
                        . '. Return image outputs only.';
                }

                $result = $this->callGeminiGenerateContent($apiKey, $candidateModel, $payload, $siteTimeout > 0 ? $siteTimeout : 120);
                $httpCode = (int) ($result['http_code'] ?? 0);
                $raw = is_array($result['raw'] ?? null) ? $result['raw'] : [];

                if ($httpCode >= 400) {
                    $msg = $this->buildGeminiErrorMessage($httpCode, $raw);
                    if ($httpCode === 404) {
                        $lastError = $msg;
                        break;
                    }

                    throw new \RuntimeException($msg);
                }

                $images = $this->extractGeminiImages($raw);
                if (! empty($images)) {
                    $addUnique($images);
                }

                if (count($collected) >= $targetCount) {
                    return array_slice($collected, 0, $targetCount);
                }

                $lastError = 'No generated images returned by Gemini.';
            }
        }

        if (! empty($collected)) {
            return array_slice($collected, 0, $targetCount);
        }

        if ($lastError === '') {
            $lastError = 'Gemini image generation failed. Please verify the configured image model in AdminCP.';
        }

        throw new \RuntimeException($lastError);
    }

    private function callGeminiGenerateContent(string $apiKey, string $model, array $payload, int $timeout): array
    {
        $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . rawurlencode($model) . ':generateContent?key=' . rawurlencode($apiKey);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $endpoint);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout > 0 ? $timeout : 120);

        $response = curl_exec($ch);
        if ($response === false) {
            $error = curl_error($ch);
            curl_close($ch);
            throw new \RuntimeException('Gemini request failed: ' . $error);
        }

        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        $raw = json_decode($response, true);
        if (! is_array($raw)) {
            throw new \RuntimeException('Gemini returned invalid JSON.');
        }

        return [
            'http_code' => $httpCode,
            'raw' => $raw,
        ];
    }

    private function buildGeminiErrorMessage(int $httpCode, array $raw): string
    {
        $msg = 'Gemini error (HTTP ' . $httpCode . ')';
        if (isset($raw['error']['message'])) {
            $msg .= ': ' . $raw['error']['message'];
        }

        return $msg;
    }

    private function extractGeminiImages(array $raw): array
    {
        $images = [];
        if (isset($raw['candidates']) && is_array($raw['candidates'])) {
            foreach ($raw['candidates'] as $candidate) {
                $parts = $candidate['content']['parts'] ?? [];
                if (! is_array($parts)) {
                    continue;
                }
                foreach ($parts as $part) {
                    $inline = $part['inlineData'] ?? $part['inline_data'] ?? null;
                    if (! is_array($inline)) {
                        continue;
                    }
                    $imgMime = (string) ($inline['mimeType'] ?? $inline['mime_type'] ?? 'image/png');
                    $imgData = (string) ($inline['data'] ?? '');
                    if ($imgData !== '') {
                        $images[] = 'data:' . $imgMime . ';base64,' . $imgData;
                    }
                }
            }
        }

        return $images;
    }

    private function configSiteValue(string $directKey, string $legacyKey, $default = '')
    {
        $direct = config($directKey, null);
        if (is_array($direct)) {
            if (array_key_exists('value', $direct) && $direct['value'] !== '') {
                return $direct['value'];
            }
        } elseif ($direct !== null && $direct !== '') {
            return $direct;
        }

        $legacy = config($legacyKey, $default);
        if (is_array($legacy)) {
            if (array_key_exists('value', $legacy) && $legacy['value'] !== '') {
                return $legacy['value'];
            }

            return $default;
        }

        return $legacy;
    }

    private function normalizeGeminiModelName(string $model): string
    {
        $model = trim($model);
        if ($model === '') {
            return $model;
        }

        // Admins may paste ListModels names like "models/gemini-2.5-flash-image".
        if (stripos($model, 'models/') === 0) {
            $model = substr($model, 7);
        }

        return trim($model);
    }

    private function injectDataUrlVar(string $html, string $slug): string
    {
        $dataUrl = '/page/' . $slug . '/ai-store-data';
        $inject = '<script>window.AI_STORE_DATA_URL = "' . e($dataUrl) . '";</script>';

        // Remove duplicate standalone assignment scripts injected in previous passes.
        $html = preg_replace(
            '#<script\b[^>]*>\s*window\.AI_STORE_DATA_URL\s*=\s*["\"][^"\"]+["\"]\s*;?\s*</script>\s*#i',
            '',
            $html
        ) ?? $html;

        // Normalize direct assignments inside scripts to current slug URL.
        $html = preg_replace(
            '#window\.AI_STORE_DATA_URL\s*=\s*["\"][^"\"]+["\"]#',
            'window.AI_STORE_DATA_URL = "' . addslashes($dataUrl) . '"',
            $html
        ) ?? $html;

        if (stripos($html, '</head>') !== false) {
            return str_ireplace('</head>', $inject . "\n</head>", $html);
        }

        return $inject . "\n" . $html;
    }

    private function hasDynamicStoreBinding(string $html): bool
    {
        $hasRoot = (bool) preg_match('/id\s*=\s*["\']ai-store-root["\']/i', $html);
        $hasDataUrlVar = (bool) preg_match('/window\s*\.\s*AI_STORE_DATA_URL/i', $html);
        $hasFetchBinding = $this->hasAiStoreDataFetchCall($html);

        return $hasRoot && $hasDataUrlVar && $hasFetchBinding;
    }

    private function extractBodyHtml(string $html): string
    {
        $trimmed = trim($html);
        if ($trimmed === '') {
            return '';
        }

        if (stripos($trimmed, '<html') === false) {
            return $trimmed;
        }

        if (preg_match('#<body[^>]*>([\s\S]*?)</body>#i', $trimmed, $m)) {
            return trim((string) ($m[1] ?? ''));
        }

        return $trimmed;
    }

    private function fallbackTemplate(string $slug): string
    {
        $page = \App\Models\Page::query()->where('slug', $slug)->first();
        $title = $page ? $this->plainText($page->title ?? '', 'Storefront') : 'Storefront';
        $desc = $page ? $this->plainText($page->description ?? '') : '';
        $contact = $page ? $this->plainText($page->business_contact ?? '') : '';
        $email = $page ? $this->plainText($page->business_email ?? '') : '';

        $products = collect();
        if ($page) {
            $products = \App\Models\BusinessProducts::query()
                ->where('business_id', (int) $page->id)
                ->where('status', 1)
                ->orderBy('id', 'desc')
                ->limit(24)
                ->get(['id', 'product_name', 'product_description', 'price', 'currency', 'image1', 'image2', 'image3', 'image4']);
        }

        $cards = '';
        foreach ($products as $p) {
            $name = $this->plainText($p->product_name ?? '');
            if ($name === '') {
                $name = 'Untitled Product';
            }
            $pd = $this->plainText($p->product_description ?? '');
            $price = $this->plainText($p->price ?? '0');
            $currency = $this->plainText($p->currency ?? 'USD');
            $img = trim((string) ($p->image1 ?? ''));
            if ($img === '') {
                $img = trim((string) ($p->image2 ?? ''));
            }
            if ($img === '') {
                $img = trim((string) ($p->image3 ?? ''));
            }
            if ($img === '') {
                $img = trim((string) ($p->image4 ?? ''));
            }

            $media = $img !== ''
                ? '<div class="thumb"><img src="' . e($this->resolveMediaUrl($img)) . '" alt="' . e($name) . '" /></div>'
                : '<div class="thumb">Image coming soon</div>';

            $cards .= '<article class="card">'
                . $media
                . '<div class="body">'
                . '<h3 class="name">' . e($name) . '</h3>'
                . '<p class="desc">' . e($pd !== '' ? $pd : 'Product details will appear here.') . '</p>'
                . '<div class="row"><div class="price">' . e($currency . ' ' . $price) . '</div></div>'
                . '</div>'
                . '</article>';
        }

        $productsCount = (int) $products->count();
        $metaContact = $contact !== '' ? '<span class="pill">Contact: ' . e($contact) . '</span>' : '';
        $metaEmail = $email !== '' ? '<span class="pill">Email: ' . e($email) . '</span>' : '';

        return "<!doctype html>\n"
            . "<html lang=\"en\">\n"
            . "<head>\n"
            . "<meta charset=\"utf-8\" />\n"
            . "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
            . "<title>" . e($title) . "</title>\n"
            . "<!-- AI_STORE_FALLBACK_TEMPLATE -->\n"
            . '<style>'
            . ':root{--bg:#f6f7fb;--surface:#ffffff;--ink:#13233a;--muted:#5f6d82;--brand:#ea8f2f;--brand2:#f3bf3d;--shadow:0 14px 34px rgba(16,33,60,.12)}'
            . '*{box-sizing:border-box}'
            . 'body{margin:0;font-family:"Segoe UI",Tahoma,Geneva,Verdana,sans-serif;background:radial-gradient(1200px 600px at -10% -10%,#ffe3bd 0%,rgba(255,227,189,0) 60%),radial-gradient(1200px 600px at 110% -5%,#dce8ff 0%,rgba(220,232,255,0) 55%),var(--bg);color:var(--ink)}'
            . '.wrap{max-width:1200px;margin:0 auto;padding:22px}'
            . '.hero{position:relative;overflow:hidden;background:linear-gradient(135deg,#1f365a,#162845);border-radius:20px;padding:28px;color:#fff;box-shadow:var(--shadow)}'
            . '.brand{display:inline-flex;align-items:center;gap:8px;padding:6px 12px;border-radius:999px;background:rgba(255,255,255,.14);font-size:12px;letter-spacing:.08em;text-transform:uppercase}'
            . '.brand-dot{width:8px;height:8px;border-radius:999px;background:var(--brand2)}'
            . '.hero h1{margin:12px 0 8px;font-size:clamp(28px,4vw,42px);line-height:1.08}'
            . '.hero p{margin:0;max-width:760px;color:#d9e3f3;font-size:16px;line-height:1.5}'
            . '.meta{margin-top:16px;display:flex;flex-wrap:wrap;gap:10px}'
            . '.pill{background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.2);padding:7px 12px;border-radius:999px;font-size:12px;color:#ecf2fb}'
            . '.section-head{display:flex;align-items:end;justify-content:space-between;margin:20px 2px 12px}'
            . '.section-head h2{margin:0;font-size:24px}'
            . '.section-head .count{font-size:13px;color:var(--muted)}'
            . '.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:16px}'
            . '.card{background:var(--surface);border-radius:16px;overflow:hidden;border:1px solid #edf1f7;box-shadow:0 10px 24px rgba(15,27,46,.08)}'
            . '.thumb{height:190px;background:linear-gradient(140deg,#eff4ff,#f7f8fb);display:grid;place-items:center;color:#93a2b8;font-size:13px}'
            . '.thumb img{width:100%;height:100%;object-fit:cover;display:block}'
            . '.body{padding:14px 14px 16px}'
            . '.name{margin:0 0 6px;font-size:18px;line-height:1.2;color:#182a45}'
            . '.desc{margin:0 0 10px;color:#647489;font-size:13px;line-height:1.45;min-height:36px}'
            . '.row{display:flex;align-items:center;justify-content:space-between;gap:10px}'
            . '.price{font-weight:800;font-size:18px;color:#0f2140}'
            . '.empty{background:var(--surface);border:1px dashed #d6deeb;border-radius:14px;padding:24px;text-align:center;color:var(--muted)}'
            . '@media (max-width:640px){.wrap{padding:14px}.hero{padding:20px;border-radius:14px}.section-head h2{font-size:20px}}'
            . "</style>\n"
            . "</head>\n"
            . "<body>\n"
            . "<main class=\"wrap\" id=\"ai-store-root\">\n"
            . '<section class="hero"><span class="brand"><span class="brand-dot"></span>Storefront</span><h1>' . e($title) . '</h1><p>' . e($desc !== '' ? $desc : 'Discover featured products curated for you.') . '</p><div class="meta">' . $metaContact . $metaEmail . '<span class="pill">Products: ' . e($productsCount) . '</span></div></section>'
            . '<section class="section-head"><h2>Featured Products</h2><span class="count">' . e($productsCount) . ' items</span></section>'
            . ($cards !== '' ? '<section class="grid">' . $cards . '</section>' : '<div class="empty">No products yet. Add products in your dashboard and regenerate.</div>')
            . "\n</main>\n"
            . "</body>\n"
            . "</html>";
    }

    private function resolveMediaUrl(string $path): string
    {
        $path = trim($path);
        if ($path === '') {
            return '';
        }

        if (preg_match('#^https?://#i', $path) || strpos($path, 'data:') === 0) {
            return $path;
        }

        try {
            return $this->normalizeMediaUrl((string) \Image::url($path));
        } catch (\Throwable $e) {
            return $this->normalizeMediaUrl($path);
        }
    }

    private function normalizeMediaUrl(string $value): string
    {
        $value = trim($value);
        if ($value === '') {
            return '';
        }

        if (preg_match('#^(https?:)?//#i', $value) || strpos($value, 'data:') === 0) {
            return $value;
        }

        if ($value[0] === '/' || $value[0] === '#') {
            return $value;
        }

        return '/' . ltrim($value, '/');
    }

    private function ensureValidRenderableHtmlOrFallback(string $html, string $slug): string
    {
        if (! $this->containsCriticalTemplateArtifacts($html)) {
            return $html;
        }

        Log::warning('AI generated template artifacts; continuing with downstream rescue sanitization.', [
            'slug' => $slug,
            'html_length' => strlen((string) $html),
        ]);

        return $html;
    }

    private function plainText($value, string $default = ''): string
    {
        $text = trim((string) $value);
        if ($text === '') {
            return $default;
        }

        $decoded = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $stripped = trim(preg_replace('/\s+/u', ' ', strip_tags($decoded)) ?? '');

        return $stripped !== '' ? $stripped : $default;
    }

    private function containsUnrenderedTemplateArtifacts(string $html): bool
    {
        $scan = (string) $html;

        // Ignore script/style blocks to reduce false positives from legitimate JS source code.
        $scan = preg_replace('#<script\b[^>]*>[\s\S]*?</script>#i', ' ', $scan);
        $scan = preg_replace('#<style\b[^>]*>[\s\S]*?</style>#i', ' ', $scan);
        if (! is_string($scan)) {
            $scan = (string) $html;
        }

        $patterns = [
            '/\+\s*esc\s*\(/i',
            '/esc\s*\([^\)]*\)\s*\+/i',
            '/["\']\s*\+\s*[a-zA-Z_$][\w$]*\s*\+/i',
            '/logo\s*\?\s*\(\s*["\']Store\s+logo["\']\s*\)\s*:\s*["\']\s*["\']\s*\)\s*\+/i',
        ];

        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $scan)) {
                return true;
            }
        }

        return false;
    }

    private function looksTruncatedHtml(string $html): bool
    {
        $raw = (string) $html;
        $scan = strtolower($raw);
        if ($scan === '') {
            return false;
        }

        $openScript = preg_match_all('#<script\b[^>]*>#i', $raw, $m1);
        $closeScript = preg_match_all('#</script>#i', $raw, $m2);
        $openScript = (int) $openScript;
        $closeScript = (int) $closeScript;

        if ($openScript <= $closeScript) {
            return false;
        }

        // Only treat as truncated when script imbalance is combined with missing
        // document closures, otherwise this is often a false positive from AI text.
        $hasBodyClose = stripos($scan, '</body>') !== false;
        $hasHtmlClose = stripos($scan, '</html>') !== false;
        if (! $hasBodyClose || ! $hasHtmlClose) {
            return true;
        }

        $tail = strtolower(substr(rtrim($raw), -1200));
        if (strpos($tail, '</script>') === false) {
            return true;
        }

        return false;
    }

    private function repairLikelyTruncatedHtml(string $html): string
    {
        $out = (string) $html;
        if ($out === '') {
            return $out;
        }

        $openScript = substr_count(strtolower($out), '<script');
        $closeScript = substr_count(strtolower($out), '</script>');
        if ($openScript > $closeScript) {
            $out .= str_repeat('</script>', $openScript - $closeScript);
        }

        if (stripos($out, '<html') !== false) {
            if (stripos($out, '</body>') === false) {
                $out .= '</body>';
            }
            if (stripos($out, '</html>') === false) {
                $out .= '</html>';
            }
        }

        return $out;
    }

    private function containsCriticalTemplateArtifacts(string $html): bool
    {
        $raw = (string) $html;
        if ($raw === '') {
            return false;
        }

        // Ignore script/style blocks so legitimate JS string assembly doesn't trigger fallback.
        $scan = preg_replace('#<script\b[^>]*>[\s\S]*?</script>#i', ' ', $raw);
        $scan = preg_replace('#<style\b[^>]*>[\s\S]*?</style>#i', ' ', (string) $scan);
        if (! is_string($scan) || $scan === '') {
            $scan = $raw;
        }

        $patterns = [
            '/\+\s*esc\s*\(/i',
            '/esc\s*\(\s*title\s*\)/i',
            '/\(\s*logo\s*\?\s*\(/i',
            '/Store\s+logo\s*["\']\s*\)\s*:\s*["\']\s*\)\s*\+/i',
            '/["\']\s*\+\s*esc\s*\(/i',
            '/\+\s*["\']\s*\+\s*["\']/i',
        ];

        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $scan)) {
                return true;
            }
        }

        // If text contains templating fragments but almost no real HTML tags, treat as broken.
        if (preg_match('/\besc\s*\(/i', $scan)) {
            $tagCount = preg_match_all('/<\s*[a-zA-Z][^>]*>/', $scan, $m);
            if ((int) $tagCount < 3) {
                return true;
            }
        }

        return false;
    }

    private function normalizeGeneratedHtml(string $html): string
    {
        $html = trim($html);
        if ($html === '') {
            return '';
        }

        // Strip markdown code fences if model wraps HTML in ```html ... ```.
        $html = preg_replace('/^```(?:html)?\s*/i', '', $html) ?? $html;
        $html = preg_replace('/\s*```$/', '', $html) ?? $html;
        $html = trim($html);

        // Handle JSON-encoded strings such as "<html>...".
        if ((substr($html, 0, 1) === '"' && substr($html, -1) === '"') || (substr($html, 0, 1) === "'" && substr($html, -1) === "'")) {
            $decoded = json_decode($html, true);
            if (is_string($decoded) && trim($decoded) !== '') {
                $html = trim($decoded);
            }
        }

        // Convert escaped newlines/tabs frequently returned by models.
        if (strpos($html, '\\n') !== false || strpos($html, '\\t') !== false || strpos($html, '\\r') !== false) {
            $html = str_replace(["\\r", "\\n", "\\t"], ["\r", "\n", "\t"], $html);
        }

        // Decode entities if HTML tags are encoded (&lt;div&gt;...).
        if (strpos($html, '<') === false && (stripos($html, '&lt;') !== false || stripos($html, '&gt;') !== false)) {
            $html = html_entity_decode($html, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        }

        // If model adds explanation text around HTML, extract the document part.
        $lower = strtolower($html);
        $docPos = strpos($lower, '<!doctype');
        if ($docPos === false) {
            $docPos = strpos($lower, '<html');
        }
        if ($docPos !== false && $docPos > 0) {
            $html = substr($html, $docPos);
            $lower = strtolower($html);
        }

        $htmlPos = strpos($lower, '<html');
        $endPos = strripos($lower, '</html>');
        if ($htmlPos !== false && $endPos !== false && $endPos > $htmlPos) {
            if ($docPos === false && $htmlPos > 0) {
                $html = substr($html, $htmlPos, ($endPos + 7) - $htmlPos);
            } else {
                $html = substr($html, 0, $endPos + 7);
            }
        }

        return trim($html);
    }

    private function extractFinishReason(array $result): string
    {
        $raw = $result['raw'] ?? null;
        if (! is_array($raw)) {
            return '';
        }

        $reason = $raw['choices'][0]['finish_reason'] ?? '';

        return strtolower(trim((string) $reason));
    }

    private function adaptiveInitialGenerationMaxTokens(bool $isUpdateRequest, int $currentHtmlLength, int $promptLength): int
    {
        $configured = (int) config('deepseek.max_tokens', 6000);
        if ($configured <= 0) {
            $configured = 6000;
        }

        $target = max(7000, $configured);
        if ($isUpdateRequest) {
            $target += 600;
        }
        if ($currentHtmlLength > 12000) {
            $target += 500;
        }
        if ($promptLength > 1200) {
            $target += 300;
        }

        return min(10000, $target);
    }

    private function adaptiveContinuationMaxTokens(int $hop, int $partialLength): int
    {
        $configured = (int) config('deepseek.max_tokens', 6000);
        if ($configured <= 0) {
            $configured = 6000;
        }

        $target = max(5200, $configured);
        if ($partialLength > 18000) {
            $target = max($target, 7000);
        }

        // Grow token budget per continuation hop to reduce repeated truncation.
        $target += max(0, ($hop - 1)) * 1200;

        return min(12000, $target);
    }

    private function requestContinuationFromTruncatedResponse(string $system, string $originalUserPrompt, string $partialHtml, int $hop = 1): array
    {
        $partialHtml = (string) $partialHtml;
        if ($partialHtml === '') {
            return [
                'tail' => '',
                'finish_reason' => '',
                'max_tokens' => 0,
            ];
        }

        $continuationMaxTokens = $this->adaptiveContinuationMaxTokens($hop, strlen($partialHtml));

        $tailContext = substr($partialHtml, -5000);
        $requestSummary = trim((string) $originalUserPrompt);
        if (strlen($requestSummary) > 1200) {
            $requestSummary = substr($requestSummary, 0, 700)
                . "\n...\n"
                . substr($requestSummary, -400);
        }

        $continuationSystem = $system . "\n"
            . 'Your previous response was cut because of token limit. '
            . 'Return ONLY the missing continuation text needed to complete the SAME HTML document. '
            . 'Do NOT restart from the beginning. Do NOT repeat already returned text. '
            . 'Do NOT stop mid-script or mid-tag. '
            . 'No markdown, no code fences, no explanation.';

        $continuationUser = "Continue the previous HTML from the exact stopping point."
            . "\nRequest summary:\n" . $requestSummary
            . "\n\nAlready returned HTML tail (for continuity):\n" . $tailContext
            . "\n\nReturn only the remaining tail content.";

        try {
            $result = $this->deepSeek->chat([
                ['role' => 'system', 'content' => $continuationSystem],
                ['role' => 'user', 'content' => $continuationUser],
            ], [
                'temperature' => 0.1,
                'max_tokens' => $continuationMaxTokens,
                'timeout' => 120,
                'connect_timeout' => 12,
                'retry_attempts' => 2,
                'request_reasoning' => false,
            ]);

            $tail = trim((string) ($result['content'] ?? ''));
            $tail = preg_replace('/^```(?:html)?\s*/i', '', $tail) ?? $tail;
            $tail = preg_replace('/\s*```$/', '', $tail) ?? $tail;

            return [
                'tail' => trim((string) $tail),
                'finish_reason' => $this->extractFinishReason($result),
                'max_tokens' => $continuationMaxTokens,
            ];
        } catch (\Throwable $e) {
            Log::warning('DeepSeek continuation request failed.', [
                'exception' => $e,
                'hop' => $hop,
                'max_tokens' => $continuationMaxTokens,
            ]);

            return [
                'tail' => '',
                'finish_reason' => '',
                'max_tokens' => $continuationMaxTokens,
            ];
        }
    }

    private function completeTruncatedHtmlResponse(
        string $system,
        string $originalUserPrompt,
        string $rawContent,
        string $logMessage,
        array $baseLog = []
    ): string {
        $current = (string) $rawContent;
        for ($hop = 1; $hop <= 8; $hop++) {
            if ($this->looksLikeClosedHtmlDocument($current)) {
                break;
            }

            $cont = $this->requestContinuationFromTruncatedResponse($system, $originalUserPrompt, $current, $hop);
            $tail = (string) ($cont['tail'] ?? '');
            $finishReason = strtolower(trim((string) ($cont['finish_reason'] ?? '')));
            $usedMaxTokens = (int) ($cont['max_tokens'] ?? 0);

            Log::warning($logMessage, array_merge($baseLog, [
                'partial_length' => strlen($current),
                'hop' => $hop,
                'continuation_max_tokens' => $usedMaxTokens,
                'continuation_finish_reason' => $finishReason,
            ]));

            if ($tail === '') {
                break;
            }

            $merged = $this->mergeContinuedHtml($current, $tail);
            if ($merged === $current) {
                break;
            }

            $current = $merged;

            if ($finishReason !== 'length' && $this->looksLikeClosedHtmlDocument($current)) {
                break;
            }
        }

        return $current;
    }

    private function looksLikeClosedHtmlDocument(string $html): bool
    {
        $scan = strtolower(trim((string) $html));
        if ($scan === '') {
            return false;
        }

        if (strpos($scan, '</html>') !== false) {
            return true;
        }

        return strpos($scan, '</body>') !== false && strpos($scan, '</script>') !== false;
    }

    private function mergeContinuedHtml(string $first, string $second): string
    {
        $a = (string) $first;
        $b = trim((string) $second);
        if ($a === '' || $b === '') {
            return $a !== '' ? $a : $b;
        }

        // If continuation accidentally returned a full HTML document, prefer it when larger.
        if ((stripos($b, '<!doctype') !== false || stripos($b, '<html') !== false) && strlen($b) > (int) (strlen($a) * 0.8)) {
            return $b;
        }

        if (strpos($a, $b) !== false) {
            return $a;
        }

        $maxOverlap = min(2500, strlen($a), strlen($b));
        for ($i = $maxOverlap; $i >= 40; $i--) {
            if (substr($a, -$i) === substr($b, 0, $i)) {
                return $a . substr($b, $i);
            }
        }

        return $a . $b;
    }

    private function buildConversationContext(array $conversation): string
    {
        if (empty($conversation)) {
            return '- (no previous messages)';
        }

        $lines = [];
        $slice = array_slice($conversation, -12);
        foreach ($slice as $turn) {
            if (! is_array($turn)) {
                continue;
            }

            $role = strtolower(trim((string) ($turn['role'] ?? 'user')));
            $content = trim((string) ($turn['content'] ?? ''));
            if ($content === '') {
                continue;
            }

            if (strlen($content) > 1200) {
                $content = substr($content, 0, 1200) . '...';
            }

            if (! in_array($role, ['user', 'assistant'], true)) {
                $role = 'user';
            }

            $lines[] = '- ' . $role . ': ' . $content;
        }

        if (empty($lines)) {
            return '- (no previous messages)';
        }

        return implode("\n", $lines);
    }

    private function buildPersistentUserDirectives(string $prompt, array $conversation, string $currentHtml): string
    {
        $directives = [];

        $latest = trim((string) $prompt);
        if ($latest !== '') {
            $directives[] = 'Latest request: ' . $latest;
        }

        $userTurns = [];
        foreach ($conversation as $turn) {
            if (! is_array($turn)) {
                continue;
            }

            $role = strtolower(trim((string) ($turn['role'] ?? '')));
            if ($role !== 'user') {
                continue;
            }

            $content = trim((string) ($turn['content'] ?? ''));
            if ($content !== '') {
                $userTurns[] = $content;
            }
        }

        $userTurns = array_slice($userTurns, -20);
        foreach ($userTurns as $content) {
            $parts = preg_split('/[\r\n]+/', $content) ?: [$content];
            foreach ($parts as $part) {
                $line = trim((string) $part);
                if ($line === '') {
                    continue;
                }

                if (preg_match('/\b(make|change|set|use|keep|update|turn|add|remove|replace|reflect|apply|don\'t|do not|must)\b/i', $line)) {
                    if (strlen($line) > 220) {
                        $line = substr($line, 0, 220) . '...';
                    }
                    $directives[] = $line;
                }
            }
        }

        if (stripos($currentHtml, 'ai-store-red-product-button-override') !== false
            || stripos($currentHtml, 'view-product-btn') !== false) {
            $directives[] = 'Keep product action buttons styled red and labeled "View product" when applicable.';
        }

        if (empty($directives)) {
            return '- keep prior approved edits while applying the latest request.';
        }

        $unique = [];
        foreach ($directives as $item) {
            $k = strtolower(trim((string) $item));
            if ($k === '' || isset($unique[$k])) {
                continue;
            }
            $unique[$k] = '- ' . trim((string) $item);
        }

        return implode("\n", array_values($unique));
    }

    private function ensureFullHtmlDocument(string $html): string
    {
        $html = trim($html);
        if ($html === '') {
            return '';
        }

        if (stripos($html, '<html') !== false) {
            return $html;
        }

        return '<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /></head><body>'
            . $html
            . '</body></html>';
    }

    private function repairDynamicBindingWithAi(string $html, string $slug): string
    {
        $dataUrl = '/page/' . $slug . '/ai-store-data';

        $system = implode("\n", [
            'You are an HTML repair assistant for storefront pages.',
            'Return ONLY HTML. No markdown. No backticks. No explanation.',
            'Preserve the current visual design as much as possible.',
            'Add dynamic binding requirements:',
            '1) Ensure one element with exact token id="ai-store-root" exists.',
            '2) Ensure JavaScript contains exact token window.AI_STORE_DATA_URL set to the provided URL.',
            '3) Ensure JS includes fetch(window.AI_STORE_DATA_URL...) and renders products dynamically into ai-store-root.',
            'Do not remove existing styling unless necessary.',
        ]);

        $user = "Fix this HTML to satisfy required binding while preserving design. Data URL: {$dataUrl}\n\nHTML:\n" . $html;

        try {
            $result = $this->deepSeek->chat([
                ['role' => 'system', 'content' => $system],
                ['role' => 'user', 'content' => $user],
            ], [
                'temperature' => 0.1,
                'max_tokens' => 4200,
                'timeout' => 120,
                'connect_timeout' => 12,
                'retry_attempts' => 2,
                'request_reasoning' => false,
            ]);

            $fixed = $this->normalizeGeneratedHtml((string) ($result['content'] ?? ''));
            if ($fixed === '') {
                return $html;
            }

            $fixed = $this->ensureFullHtmlDocument($fixed);
            $fixed = $this->injectDataUrlVar($fixed, $slug);

            return $fixed;
        } catch (\Throwable $e) {
            Log::warning('AI dynamic binding repair fallback triggered.', [
                'slug' => $slug,
                'exception' => $e,
            ]);
            return $html;
        }
    }

    private function enforceDynamicStoreBinding(string $html, string $slug): string
    {
        $html = $this->ensureFullHtmlDocument($html);
        $html = $this->injectDataUrlVar($html, $slug);

        if (! preg_match('/id\s*=\s*["\']ai-store-root["\']/i', $html)) {
            $root = '<div id="ai-store-root"></div>';
            if (stripos($html, '</body>') !== false) {
                $html = str_ireplace('</body>', $root . "\n</body>", $html);
            } else {
                $html .= "\n" . $root;
            }
        }

        if (! $this->hasAiStoreDataFetchCall($html)) {
            $shim = '<script>(function(){if(!window.AI_STORE_DATA_URL){return;}fetch(window.AI_STORE_DATA_URL,{credentials:"same-origin"}).then(function(r){return r.json();}).then(function(data){window.__AI_STORE_DATA__=data;}).catch(function(){});})();</script>';
            if (stripos($html, '</body>') !== false) {
                $html = str_ireplace('</body>', $shim . "\n</body>", $html);
            } else {
                $html .= "\n" . $shim;
            }
        }

        return $html;
    }

    private function polishDesignWithAi(string $html, string $slug, string $latestPrompt = ''): string
    {
        $dataUrl = '/page/' . $slug . '/ai-store-data';

        $system = implode("\n", [
            'You are a premium frontend design improver for storefront HTML.',
            'Return ONLY HTML. No markdown. No backticks. No explanation.',
            'Keep dynamic behavior intact while improving visual quality significantly.',
            'Preserve required tokens exactly: id="ai-store-root" and window.AI_STORE_DATA_URL and fetch(window.AI_STORE_DATA_URL...).',
            'Improve typography, spacing, color harmony, section rhythm, cards, and mobile responsiveness.',
            'Avoid generic/plain look and avoid giant empty placeholder blocks.',
            'Keep content/data mapping logic fully dynamic.',
        ]);

        $user = "Polish this storefront to look premium while preserving functionality."
            . ($latestPrompt !== '' ? "\nLatest user direction: " . $latestPrompt : '')
            . "\nData URL must remain: {$dataUrl}"
            . "\n\nHTML:\n" . $html;

        try {
            $result = $this->deepSeek->chat([
                ['role' => 'system', 'content' => $system],
                ['role' => 'user', 'content' => $user],
            ], [
                'temperature' => 0.45,
                'max_tokens' => 4200,
                'timeout' => 120,
                'connect_timeout' => 12,
                'retry_attempts' => 2,
                'request_reasoning' => false,
            ]);

            $polished = $this->normalizeGeneratedHtml((string) ($result['content'] ?? ''));
            if ($polished === '') {
                return '';
            }

            $polished = $this->ensureFullHtmlDocument($polished);
            $polished = $this->injectDataUrlVar($polished, $slug);

            return $polished;
        } catch (\Throwable $e) {
            Log::warning('AI design polish fallback triggered.', [
                'slug' => $slug,
                'exception' => $e,
            ]);
            return '';
        }
    }

    private function trimHtmlContextForPrompt(string $html, int $maxChars = 50000): string
    {
        $html = trim($html);
        if ($html === '' || strlen($html) <= $maxChars) {
            return $html;
        }

        $headLen = (int) floor($maxChars * 0.6);
        $tailLen = max(0, $maxChars - $headLen);

        $head = substr($html, 0, $headLen);
        $tail = $tailLen > 0 ? substr($html, -$tailLen) : '';

        return $head
            . "\n\n<!-- CURRENT_HTML_TRUNCATED_FOR_MODEL_CONTEXT -->\n\n"
            . $tail;
    }

    private function injectLogoHydrationScript(string $html): string
    {
        // If storefront already fetches AI store data, avoid injecting another fetch path.
        if ($this->hasAiStoreDataFetchCall($html)) {
            return $html;
        }

        $marker = 'window.__AI_STORE_LOGO_HYDRATE__';
        if (strpos($html, $marker) !== false) {
            return $html;
        }

        $script = '<script>(function(){window.__AI_STORE_LOGO_HYDRATE__=1;'
            . 'function pickLogo(data){var s=(data&&data.store)||{};return String(s.theme2_logo||s.logo||"").trim();}'
            . 'function applyLogo(url){if(!url){return;}var selectors=["header img","nav img",".logo-area img",".nav-logo img",".logo img","[data-ai-logo]"];for(var i=0;i<selectors.length;i++){var el=document.querySelector(selectors[i]);if(el&&el.tagName&&el.tagName.toLowerCase()==="img"){el.setAttribute("src",url);if(!el.getAttribute("alt")){el.setAttribute("alt","Business logo");}return;}}}'
            . 'if(!window.AI_STORE_DATA_URL){return;}'
            . 'fetch(window.AI_STORE_DATA_URL,{credentials:"same-origin"}).then(function(r){return r.json();}).then(function(data){applyLogo(pickLogo(data));}).catch(function(){});'
            . '})();</script>';

        if (stripos($html, '</body>') !== false) {
            return str_ireplace('</body>', $script . "\n</body>", $html);
        }

        return $html . "\n" . $script;
    }

    private function injectRuntimeRescueRendererScript(string $html): string
    {
        $marker = 'window.__AI_STORE_RUNTIME_RESCUE__';
        if (strpos($html, $marker) !== false) {
            return $html;
        }

        $script = <<<'HTML'
<script>
(function(){
    if (window.__AI_STORE_RUNTIME_RESCUE__) { return; }
    window.__AI_STORE_RUNTIME_RESCUE__ = 1;

    function esc(v){
        return String(v == null ? '' : v).replace(/[&<>"']/g, function(s){
            return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[s];
        });
    }

    function text(v){
        var s = String(v == null ? '' : v);
        try {
            var ta = document.createElement('textarea');
            ta.innerHTML = s;
            s = ta.value || s;
        } catch (e) {}
        return s.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
    }

    function safeHtml(v){
        var s = String(v == null ? '' : v);
        if (!s) { return ''; }

        try {
            var ta = document.createElement('textarea');
            ta.innerHTML = s;
            s = ta.value || s;
        } catch (e) {}

        var tmp = document.createElement('div');
        tmp.innerHTML = s;

        var allowed = {
            A: 1, B: 1, STRONG: 1, I: 1, EM: 1, U: 1,
            P: 1, BR: 1, UL: 1, OL: 1, LI: 1, SPAN: 1, DIV: 1
        };

        var nodes = tmp.querySelectorAll('*');
        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];
            var tag = String(node.tagName || '').toUpperCase();
            if (!allowed[tag]) {
                var textNode = document.createTextNode(node.textContent || '');
                if (node.parentNode) {
                    node.parentNode.replaceChild(textNode, node);
                }
                continue;
            }

            var attrs = Array.prototype.slice.call(node.attributes || []);
            for (var j = 0; j < attrs.length; j++) {
                var attr = attrs[j];
                var name = String(attr.name || '').toLowerCase();
                if (tag === 'A' && name === 'href') {
                    continue;
                }
                node.removeAttribute(attr.name);
            }

            if (tag === 'A') {
                var href = String(node.getAttribute('href') || '').trim();
                if (!/^(https?:|mailto:|tel:|\/|#)/i.test(href)) {
                    node.setAttribute('href', '#');
                }
                node.setAttribute('target', '_blank');
                node.setAttribute('rel', 'noopener noreferrer');
            }
        }

        return String(tmp.innerHTML || '').trim();
    }

    function rootEl(){
        return document.getElementById('ai-store-root');
    }

    function hasBrokenTemplateArtifacts(el){
        if (!el) { return false; }

        var html = String(el.innerHTML || '');
        if (!html) { return false; }

        // Detect un-evaluated template fragments such as "+ esc(title) +" or src="' + esc(logo) + '".
        if (/(\+\s*esc\s*\()|(esc\s*\([^\)]*\)\s*\+)|(["']\s*\+\s*[a-zA-Z_$][\w$]*\s*\+)/i.test(html)) {
            return true;
        }

        try {
            var badImg = el.querySelector('img[src*="esc("]') || el.querySelector('img[src*="+ "]') || el.querySelector('img[src*=" +"]') || el.querySelector('img[src*="+"]');
            if (badImg) {
                return true;
            }
        } catch (e) {}

        return false;
    }

    function hasRenderableContent(el){
        if (!el) { return false; }
        if (hasBrokenTemplateArtifacts(el)) { return false; }
        var rawText = String(el.textContent || '').toLowerCase();
        if (rawText.indexOf('loading products') !== -1 || rawText.indexOf('loading address') !== -1 || rawText.indexOf('loading email') !== -1) {
            return false;
        }
        // Raw markup-like text in content means broken rendering, not valid UI.
        if (/(<\/?\s*(ul|ol|li|a|strong|em|p|br)\b|&lt;\/?\s*(ul|ol|li|a|strong|em|p|br)\b)/i.test(rawText)) {
            return false;
        }
        try {
            if (el.querySelector('.ai-rescue-card,[data-product-id],.product-card,.product-item,[class*="product-card"],[class*="product-item"]')) {
                return true;
            }
        } catch (e) {}
        try {
            if ((rawText.indexOf('products') !== -1 || rawText.indexOf('featured') !== -1)
                && !el.querySelector('img,[class*="media"],figure,.card,.product')) {
                return false;
            }
        } catch (e2) {}
        if (el.querySelector('img,video,canvas,svg,iframe,a,button,input,select,textarea,article,section,main')) {
            return true;
        }
        var t = text(el.textContent || '');
        return t.length > 300;
    }

    function pickImages(p){
        var out = [];
        if (!p || typeof p !== 'object') { return out; }
        var list = [p.image1, p.image2, p.image3, p.image4, p.image];
        if (Array.isArray(p.images)) {
            list = list.concat(p.images);
        }
        for (var i = 0; i < list.length; i++) {
            var val = String(list[i] || '').trim();
            if (val && out.indexOf(val) === -1) {
                out.push(val);
            }
        }
        return out;
    }

    function ensureRoot(){
        var el = rootEl();
        if (el) { return el; }
        el = document.createElement('div');
        el.id = 'ai-store-root';
        if (document.body) {
            document.body.appendChild(el);
        }
        return el;
    }

    function renderFallback(data){
        var root = ensureRoot();
        if (!root || hasRenderableContent(root)) { return; }

        var store = (data && data.store) || {};
        var products = (data && data.products && Array.isArray(data.products))
            ? data.products
            : ((data && data.products_sample && Array.isArray(data.products_sample)) ? data.products_sample : []);
        var logo = String(store.theme2_logo || store.logo || '').trim();
        var title = text(store.title || store.name || 'Store');
        var descHtml = safeHtml(store.description || '');
        var contact = text(store.contact || store.business_contact || '');
        var email = text(store.email || store.business_email || '');

        var cards = '';
        for (var i = 0; i < products.length; i++) {
            var p = products[i] || {};
            var name = text(p.product_name || p.name || p.title || ('Product ' + (i + 1)));
            var pdHtml = safeHtml(p.product_description || p.description || p.body || '');
            var price = text(p.price || 'Price on request');
            var currency = text(p.currency || '');
            var imgs = pickImages(p);
            var img = imgs.length ? imgs[0] : '';
            var productUrl = text(p.product_url || p.url || p.link || '');
            var priceText = (currency ? (currency + ' ') : '') + price;

            cards += '<article class="ai-rescue-card">'
                + '<div class="ai-rescue-media">'
                + (img ? ('<img src="' + esc(img) + '" alt="' + esc(name) + '">') : '<div class="ai-rescue-placeholder">No image</div>')
                + '</div>'
                + '<div class="ai-rescue-body">'
                + '<h3>' + esc(name) + '</h3>'
                + (pdHtml ? ('<div class="ai-rescue-rich">' + pdHtml + '</div>') : '')
                + '<div class="ai-rescue-price">' + esc(priceText) + '</div>'
                + (productUrl ? ('<a class="ai-rescue-link view-product-btn" href="' + esc(productUrl) + '">View product</a>') : '')
                + '</div>'
                + '</article>';
        }

        root.innerHTML = ''
            + '<style id="ai-store-runtime-rescue-style">'
            + '.ai-rescue-wrap{max-width:1200px;margin:0 auto;padding:22px;font-family:Segoe UI,Tahoma,Arial,sans-serif;color:#16263f}'
            + '.ai-rescue-head{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;padding:16px;border:1px solid #dfe8f6;border-radius:14px;background:#fff}'
            + '.ai-rescue-brand{display:flex;align-items:center;gap:12px;min-width:0}'
            + '.ai-rescue-brand img{width:68px;height:68px;object-fit:contain;border-radius:10px;border:1px solid #e5ecf8;background:#fff}'
            + '.ai-rescue-title{margin:0;font-size:28px;line-height:1.2}'
            + '.ai-rescue-desc{margin:6px 0 0;color:#4a5f7e}'
            + '.ai-rescue-meta{display:flex;gap:8px;flex-wrap:wrap}'
            + '.ai-rescue-chip{padding:6px 10px;border:1px solid #d8e4f6;border-radius:999px;background:#f6f9ff;font-size:12px;color:#2d4568}'
            + '.ai-rescue-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin-top:16px}'
            + '.ai-rescue-card{border:1px solid #dfe8f6;border-radius:14px;overflow:hidden;background:#fff;box-shadow:0 8px 22px rgba(9,33,70,.08)}'
            + '.ai-rescue-media{height:180px;background:#f4f7fd;display:grid;place-items:center}'
            + '.ai-rescue-media img{width:100%;height:100%;object-fit:cover}'
            + '.ai-rescue-placeholder{font-size:12px;color:#6f84a2}'
            + '.ai-rescue-body{padding:12px}'
            + '.ai-rescue-body h3{margin:0 0 6px;font-size:18px}'
            + '.ai-rescue-body p{margin:0 0 10px;color:#4a5f7e;font-size:13px;line-height:1.5}'
            + '.ai-rescue-rich{margin:0 0 10px;color:#4a5f7e;font-size:13px;line-height:1.55}'
            + '.ai-rescue-rich p{margin:0 0 8px}'
            + '.ai-rescue-rich ul,.ai-rescue-rich ol{margin:0 0 8px 18px;padding:0}'
            + '.ai-rescue-rich li{margin:0 0 4px}'
            + '.ai-rescue-price{font-weight:700;color:#0f2d56;margin-bottom:10px}'
            + '.ai-rescue-link{display:inline-block;padding:8px 12px;border-radius:999px;background:#e63946;color:#fff;text-decoration:none;font-size:12px;font-weight:700;box-shadow:0 6px 16px rgba(230,57,70,.25)}'
            + '.ai-rescue-link:hover{background:#ff4d5a;color:#fff;text-decoration:none}'
            + '.ai-rescue-empty{margin-top:16px;padding:14px;border:1px dashed #d5e2f5;border-radius:12px;background:#fff;color:#4a5f7e}'
            + '@media (max-width:768px){.ai-rescue-title{font-size:22px}}'
            + '</style>'
            + '<div class="ai-rescue-wrap">'
            + '<header class="ai-rescue-head">'
            + '<div class="ai-rescue-brand">'
            + (logo ? ('<img src="' + esc(logo) + '" alt="Store logo">') : '')
            + '<div>'
            + '<h1 class="ai-rescue-title">' + esc(title) + '</h1>'
            + (descHtml ? ('<div class="ai-rescue-desc ai-rescue-rich">' + descHtml + '</div>') : '')
            + '</div>'
            + '</div>'
            + '<div class="ai-rescue-meta">'
            + (contact ? ('<span class="ai-rescue-chip">' + esc(contact) + '</span>') : '')
            + (email ? ('<span class="ai-rescue-chip">' + esc(email) + '</span>') : '')
            + '</div>'
            + '</header>'
            + (cards ? ('<section class="ai-rescue-grid">' + cards + '</section>') : '<div class="ai-rescue-empty">No products available yet.</div>')
            + '</div>';
    }

    function run(){
        var root = ensureRoot();
        if (!root) { return; }
        if (!window.AI_STORE_DATA_URL) { return; }
        if (window.__AI_STORE_RESCUE_FETCH_INFLIGHT__) { return; }
        if (window.__AI_STORE_RESCUE_FETCH_DONE__) { return; }

        // If the page has already rendered normally, skip rescue work.
        if (window.__storeLoaded && hasRenderableContent(root)) { return; }

        // Reuse prefetched data when available to avoid race conditions where
        // data is loaded but model-rendered UI never mounts.
        if (window.__AI_STORE_DATA__) {
            if (!hasRenderableContent(root)) {
                renderFallback(window.__AI_STORE_DATA__ || {});
            }
            window.__AI_STORE_RESCUE_FETCH_DONE__ = 1;
            return;
        }

        window.__AI_STORE_RESCUE_FETCH_INFLIGHT__ = 1;

        fetch(window.AI_STORE_DATA_URL, {credentials:'same-origin'})
            .then(function(r){ return r.json(); })
            .then(function(data){
                var target = ensureRoot();
                if (!hasRenderableContent(target)) {
                    renderFallback(data || {});
                }
                window.__AI_STORE_RESCUE_FETCH_DONE__ = 1;
            })
            .catch(function(){})
            .finally(function(){
                window.__AI_STORE_RESCUE_FETCH_INFLIGHT__ = 0;
            });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function(){
            setTimeout(run, 450);
        });
    } else {
        setTimeout(run, 450);
    }
})();
</script>
HTML;

        if (stripos($html, '</body>') !== false) {
            return str_ireplace('</body>', $script . "\n</body>", $html);
        }

        return $html . "\n" . $script;
    }

        private function injectPremiumDataEnhancerScript(string $html): string
        {
            // Preserve generated design when it already has dynamic data binding/render logic.
            if ($this->hasAiStoreDataFetchCall($html)) {
                return $html;
            }

                $marker = 'window.__AI_STORE_PREMIUM_ENHANCE__';
                if (strpos($html, $marker) !== false) {
                        return $html;
                }

                $script = <<<'HTML'
<script>
(function(){
    if (window.__AI_STORE_PREMIUM_ENHANCE__) { return; }
    window.__AI_STORE_PREMIUM_ENHANCE__ = 1;

    function esc(v){
        return String(v == null ? '' : v).replace(/[&<>"']/g, function(s){
            return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[s];
        });
    }

    function text(v){
        return String(v == null ? '' : v).replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
    }

    function safeRichHtml(v){
        var s = String(v == null ? '' : v);
        if (!s) { return ''; }

        var tmp = document.createElement('div');
        tmp.innerHTML = s;

        var allowed = {
            A: 1, B: 1, STRONG: 1, I: 1, EM: 1, U: 1,
            P: 1, BR: 1, UL: 1, OL: 1, LI: 1, SPAN: 1, DIV: 1
        };

        var nodes = tmp.querySelectorAll('*');
        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];
            var tag = String(node.tagName || '').toUpperCase();
            if (!allowed[tag]) {
                var textNode = document.createTextNode(node.textContent || '');
                if (node.parentNode) {
                    node.parentNode.replaceChild(textNode, node);
                }
                continue;
            }

            var attrs = Array.prototype.slice.call(node.attributes || []);
            for (var j = 0; j < attrs.length; j++) {
                var attr = attrs[j];
                var name = String(attr.name || '').toLowerCase();
                if (tag === 'A' && name === 'href') {
                    continue;
                }
                node.removeAttribute(attr.name);
            }

            if (tag === 'A') {
                var href = String(node.getAttribute('href') || '').trim();
                if (!/^(https?:|mailto:|tel:|\/|#)/i.test(href)) {
                    node.setAttribute('href', '#');
                }
                node.setAttribute('target', '_blank');
                node.setAttribute('rel', 'noopener noreferrer');
            }
        }

        return String(tmp.innerHTML || '').trim();
    }

    function human(k){
        var key = String(k || '').replace(/_/g, ' ').replace(/\s+/g, ' ').trim();
        return key.replace(/\b\w/g, function(c){ return c.toUpperCase(); });
    }

    function pickLogo(data){
        var s = (data && data.store) || {};
        return String(s.theme2_logo || s.logo || '').trim();
    }

    function pickName(p, idx){
        var name = text((p && (p.product_name || p.name || p.title)) || '');
        if (!name) {
            var id = p && p.id ? String(p.id) : String(idx + 1);
            name = 'Product ' + id;
        }
        return name;
    }

    function pickDescription(p){
        return safeRichHtml((p && (p.product_description || p.description || p.body)) || '');
    }

    function pickProductUrl(p, store){
        var direct = text(p && (p.product_url || p.url || p.link || ''));
        if (direct) {
            return direct;
        }

        var slug = text(store && (store.slug || ''));
        var id = p && p.id != null ? String(p.id).trim() : '';
        if (!slug || !id) {
            return '';
        }

        return '/store/' + encodeURIComponent(slug) + '/product/' + encodeURIComponent(id);
    }

    function pickImages(p){
        var out = [];
        if (!p || typeof p !== 'object') { return out; }
        var list = [p.image1, p.image2, p.image3, p.image4, p.image];
        if (Array.isArray(p.images)) {
            list = list.concat(p.images);
        }
        for (var i = 0; i < list.length; i++) {
            var val = String(list[i] || '').trim();
            if (val && out.indexOf(val) === -1) {
                out.push(val);
            }
        }
        return out;
    }

    function buildMetaRows(obj, skip){
        if (!obj || typeof obj !== 'object') { return ''; }
        var skipMap = {};
        for (var i = 0; i < (skip || []).length; i++) {
            skipMap[String(skip[i])] = 1;
        }

        var rows = [];
        Object.keys(obj).sort().forEach(function(key){
            if (skipMap[key]) { return; }
            var val = obj[key];
            if (val == null || typeof val === 'object') { return; }
            var txt = text(val);
            if (!txt) { return; }
            rows.push('<div class="ai-premium-meta-item"><span class="ai-premium-meta-key">' + esc(human(key)) + '</span><span class="ai-premium-meta-value">' + esc(txt) + '</span></div>');
        });
        return rows.join('');
    }

    function formatPrice(price, currency){
        var p = String(price == null ? '' : price).trim();
        var c = text(currency || '');
        if (!p) { return 'Price on request'; }
        return (c ? c + ' ' : '') + p;
    }

    function ensureStyle(){
        if (document.getElementById('ai-premium-enhancer-style')) { return; }
        var style = document.createElement('style');
        style.id = 'ai-premium-enhancer-style';
        style.textContent = ''
            + '.ai-premium-shell{position:relative;max-width:1240px;margin:0 auto;padding:24px;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif;color:#11243d;}'
            + '.ai-premium-shell *{box-sizing:border-box;}'
            + '.ai-premium-surface{position:relative;overflow:hidden;background:linear-gradient(145deg,#f8fbff,#eef4ff 45%,#f9f5ee);border:1px solid #dbe8fb;border-radius:28px;padding:24px;box-shadow:0 20px 50px rgba(17,36,61,.12);}'
            + '.ai-premium-surface:before{content:"";position:absolute;top:-160px;right:-120px;width:360px;height:360px;border-radius:50%;background:radial-gradient(circle,#ffdca7 0%,rgba(255,220,167,0) 65%);pointer-events:none;}'
            + '.ai-premium-surface:after{content:"";position:absolute;bottom:-160px;left:-120px;width:360px;height:360px;border-radius:50%;background:radial-gradient(circle,#c8defd 0%,rgba(200,222,253,0) 68%);pointer-events:none;}'
            + '.ai-premium-hero{position:relative;z-index:1;display:grid;grid-template-columns:1.3fr .9fr;gap:18px;margin-bottom:18px;}'
            + '.ai-premium-main{background:rgba(255,255,255,.78);backdrop-filter:blur(5px);border:1px solid #e7eefb;border-radius:22px;padding:18px;}'
            + '.ai-premium-badge{display:inline-block;padding:6px 12px;border-radius:999px;background:#12345f;color:#fff;font-size:11px;letter-spacing:.1em;text-transform:uppercase;}'
            + '.ai-premium-title{margin:10px 0 8px;font-size:clamp(28px,4.3vw,44px);line-height:1.05;color:#0f2745;}'
            + '.ai-premium-description{margin:0;color:#415975;line-height:1.6;}'
            + '.ai-premium-contact{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px;}'
            + '.ai-premium-chip{padding:7px 12px;border-radius:999px;background:#f0f5ff;border:1px solid #d7e4fb;font-size:12px;color:#28405f;}'
            + '.ai-premium-logo-box{display:grid;place-items:center;background:rgba(255,255,255,.8);border:1px solid #e7eefb;border-radius:22px;padding:14px;}'
            + '.ai-premium-logo{max-width:180px;max-height:92px;object-fit:contain;filter:drop-shadow(0 8px 16px rgba(12,28,52,.18));}'
            + '.ai-premium-meta-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;margin-top:10px;}'
            + '.ai-premium-meta-item{display:flex;flex-direction:column;gap:4px;padding:10px 12px;border-radius:12px;background:#fff;border:1px solid #e5edfb;}'
            + '.ai-premium-meta-key{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#6a7f9d;}'
            + '.ai-premium-meta-value{font-size:13px;color:#1b3555;word-break:break-word;}'
            + '.ai-premium-section-title{margin:18px 2px 10px;font-size:24px;color:#0f2745;}'
            + '.ai-premium-products{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;}'
            + '.ai-premium-card{background:#fff;border:1px solid #e3ebfa;border-radius:18px;overflow:hidden;box-shadow:0 12px 26px rgba(16,34,58,.10);animation:aiPremiumRise .38s ease both;}'
            + '.ai-premium-card-media{height:180px;background:linear-gradient(145deg,#edf3ff,#f5f8ff);display:grid;place-items:center;}'
            + '.ai-premium-card-image{width:100%;height:100%;object-fit:cover;display:block;}'
            + '.ai-premium-card-placeholder{font-size:13px;color:#6f84a1;}'
            + '.ai-premium-card-body{padding:14px;}'
            + '.ai-premium-card-title{margin:0;font-size:19px;color:#0f2745;}'
            + '.ai-premium-card-desc{margin:7px 0 10px;color:#4a627f;font-size:13px;line-height:1.55;min-height:42px;}'
            + '.ai-premium-card-price{font-size:20px;font-weight:700;color:#11315b;margin-bottom:8px;}'
            + '.ai-premium-card-highlights{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px;}'
            + '.ai-premium-card-actions{display:flex;justify-content:flex-end;margin-bottom:10px;}'
            + '.ai-premium-buy-now{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:999px;background:linear-gradient(135deg,#0f2745,#1f4f88);color:#fff;text-decoration:none;font-size:12px;font-weight:700;letter-spacing:.03em;box-shadow:0 8px 18px rgba(17,49,91,.24);}'
            + '.ai-premium-buy-now:hover{background:linear-gradient(135deg,#12345f,#245d9d);color:#fff;text-decoration:none;}'
            + '.ai-premium-card-details summary{cursor:pointer;font-size:12px;color:#25486f;font-weight:600;margin-bottom:8px;}'
            + '.ai-premium-empty{padding:18px;border:1px dashed #c7d7ef;border-radius:14px;background:#fff;color:#506786;}'
            + '@media (max-width:840px){.ai-premium-hero{grid-template-columns:1fr;}.ai-premium-logo{max-width:150px;max-height:78px;}}'
            + '@keyframes aiPremiumRise{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}';
        document.head.appendChild(style);
    }

    function applyLogo(url){
        if (!url) { return; }
        var selectors = ['header img','nav img','.logo-area img','.nav-logo img','.logo img','[data-ai-logo]'];
        for (var i = 0; i < selectors.length; i++) {
            var el = document.querySelector(selectors[i]);
            if (el && el.tagName && el.tagName.toLowerCase() === 'img') {
                el.setAttribute('src', url);
                if (!el.getAttribute('alt')) {
                    el.setAttribute('alt', 'Business logo');
                }
            }
        }
    }

    function render(root, data){
        var store = (data && data.store) || {};
        var products = Array.isArray(data && data.products) ? data.products : [];
        var title = text(store.title || store.name || 'Storefront');
        var descHtml = safeRichHtml(store.description || '');
        if (!descHtml) {
            descHtml = esc('Discover premium products and complete details below.');
        }
        var contact = text(store.contact || '');
        var email = text(store.email || '');
        var address = text(store.full_address || store.address || '');
        var logo = pickLogo(data);

        var contactRow = '';
        if (contact) { contactRow += '<span class="ai-premium-chip">Contact: ' + esc(contact) + '</span>'; }
        if (email) { contactRow += '<span class="ai-premium-chip">Email: ' + esc(email) + '</span>'; }
        if (address) { contactRow += '<span class="ai-premium-chip">Address: ' + esc(address) + '</span>'; }
        contactRow += '<span class="ai-premium-chip">Products: ' + esc(products.length) + '</span>';

        var cards = products.map(function(p, idx){
            var name = pickName(p, idx);
            var description = pickDescription(p);
            var descriptionHtml = description || esc('Detailed product information available below.');
            var priceText = formatPrice(p && p.price, p && p.currency);
            var productUrl = pickProductUrl(p, store);
            var images = pickImages(p);
            var video = text(p && (p.product_video || p.video || p.product_video_raw || ''));
            var media = images.length
                ? '<img class="ai-premium-card-image" src="' + esc(images[0]) + '" alt="' + esc(name) + '">'
                : (video
                    ? '<video class="ai-premium-card-image" controls preload="metadata" playsinline><source src="' + esc(video) + '"></video>'
                    : '<div class="ai-premium-card-placeholder">No image uploaded</div>');

            var highlights = '';
            var sku = text(p && (p.item_number || p.sku || ''));
            var qty = text(p && (p.quantity_available || p.stock || ''));
            var discount = text(p && (p.discount || ''));
            var video = text(p && (p.product_video || p.video || p.product_video_raw || ''));
            if (sku) { highlights += '<span class="ai-premium-chip">SKU: ' + esc(sku) + '</span>'; }
            if (qty) { highlights += '<span class="ai-premium-chip">Stock: ' + esc(qty) + '</span>'; }
            if (discount) { highlights += '<span class="ai-premium-chip">Discount: ' + esc(discount) + '</span>'; }
            if (video) { highlights += '<span class="ai-premium-chip">Video: Available</span>'; }

            var actions = '';
            if (productUrl) {
                actions = '<div class="ai-premium-card-actions"><a class="ai-premium-buy-now" href="' + esc(productUrl) + '" target="_blank" rel="noopener">Click to Buy</a></div>';
            }

            return ''
                + '<article class="ai-premium-card">'
                + '<div class="ai-premium-card-media">' + media + '</div>'
                + '<div class="ai-premium-card-body">'
                + '<h3 class="ai-premium-card-title">' + esc(name) + '</h3>'
                + '<div class="ai-premium-card-desc">' + descriptionHtml + '</div>'
                + '<div class="ai-premium-card-price">' + esc(priceText) + '</div>'
                + '<div class="ai-premium-card-highlights">' + highlights + '</div>'
                + actions
                + '</div>'
                + '</article>';
        }).join('');

        if (!cards) {
            cards = '<div class="ai-premium-empty">No active products found. Add products to show full catalog details.</div>';
        }

        root.innerHTML = ''
            + '<section class="ai-premium-shell">'
            + '<div class="ai-premium-surface">'
            + '<div class="ai-premium-hero">'
            + '<div class="ai-premium-main">'
            + '<span class="ai-premium-badge">Premium Storefront</span>'
            + '<h1 class="ai-premium-title">' + esc(title) + '</h1>'
            + '<div class="ai-premium-description">' + descHtml + '</div>'
            + '<div class="ai-premium-contact">' + contactRow + '</div>'
            + '</div>'
            + '<div class="ai-premium-logo-box">'
            + (logo ? '<img class="ai-premium-logo" src="' + esc(logo) + '" alt="' + esc(title) + ' logo">' : '<div class="ai-premium-card-placeholder">Logo not uploaded</div>')
            + '</div>'
            + '</div>'
            + '<h2 class="ai-premium-section-title">Products</h2>'
            + '<div class="ai-premium-products">' + cards + '</div>'
            + '</div>'
            + '</section>';
    }

    if (!window.AI_STORE_DATA_URL) { return; }
    ensureStyle();
    fetch(window.AI_STORE_DATA_URL, { credentials: 'same-origin' })
        .then(function(r){ return r.json(); })
        .then(function(data){
            applyLogo(pickLogo(data));
            var root = document.getElementById('ai-store-root');
            if (root) {
                render(root, data || {});
            }
        })
        .catch(function(){});
})();
</script>
HTML;

                if (stripos($html, '</body>') !== false) {
                        return str_ireplace('</body>', $script . "\n</body>", $html);
                }

                return $html . "\n" . $script;
        }

    private function injectProductHydrationSafetyScript(string $html): string
    {
        $marker = 'window.__AI_STORE_PRODUCT_SAFETY__';
        if (strpos($html, $marker) !== false) {
            return $html;
        }

        $script = <<<'HTML'
<script>
(function(){
    if (window.__AI_STORE_PRODUCT_SAFETY__) { return; }
    window.__AI_STORE_PRODUCT_SAFETY__ = 1;

    function esc(v){
        return String(v == null ? '' : v).replace(/[&<>"']/g, function(s){
            return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[s];
        });
    }

    function text(v){
        return String(v == null ? '' : v).replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
    }

    function safeRichHtml(v){
        var s = String(v == null ? '' : v);
        if (!s) { return ''; }

        var tmp = document.createElement('div');
        tmp.innerHTML = s;

        var allowed = {
            A: 1, B: 1, STRONG: 1, I: 1, EM: 1, U: 1,
            P: 1, BR: 1, UL: 1, OL: 1, LI: 1, SPAN: 1, DIV: 1
        };

        var nodes = tmp.querySelectorAll('*');
        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];
            var tag = String(node.tagName || '').toUpperCase();
            if (!allowed[tag]) {
                var textNode = document.createTextNode(node.textContent || '');
                if (node.parentNode) {
                    node.parentNode.replaceChild(textNode, node);
                }
                continue;
            }

            var attrs = Array.prototype.slice.call(node.attributes || []);
            for (var j = 0; j < attrs.length; j++) {
                var attr = attrs[j];
                var name = String(attr.name || '').toLowerCase();
                if (tag === 'A' && name === 'href') {
                    continue;
                }
                node.removeAttribute(attr.name);
            }

            if (tag === 'A') {
                var href = String(node.getAttribute('href') || '').trim();
                if (!/^(https?:|mailto:|tel:|\/|#)/i.test(href)) {
                    node.setAttribute('href', '#');
                }
                node.setAttribute('target', '_blank');
                node.setAttribute('rel', 'noopener noreferrer');
            }
        }

        return String(tmp.innerHTML || '').trim();
    }

    function rootEl(){
        return document.getElementById('ai-store-root');
    }

    function hasProductCards(root){
        if (!root) { return false; }
        var sel = '.ai-premium-card,.product-card,[data-product-card],article[class*="product"],.product-item';
        return !!root.querySelector(sel);
    }

    function hasLoadingPlaceholder(root){
        if (!root) { return false; }
        var t = String(root.textContent || '').toLowerCase();
        return t.indexOf('loading inventory') >= 0 || t.indexOf('loading products') >= 0 || t.indexOf('loading catalog') >= 0;
    }

    function pickImages(p){
        var out = [];
        var list = [p && p.image1, p && p.image2, p && p.image3, p && p.image4, p && p.image];
        if (Array.isArray(p && p.images)) {
            list = list.concat(p.images);
        }
        for (var i = 0; i < list.length; i++) {
            var val = String(list[i] || '').trim();
            if (val && out.indexOf(val) === -1) {
                out.push(val);
            }
        }
        return out;
    }

    function pickContainer(root){
        if (!root) { return null; }
        var candidates = root.querySelectorAll('section,div,main,article');
        for (var i = 0; i < candidates.length; i++) {
            var el = candidates[i];
            var t = String(el.textContent || '').toLowerCase();
            if (t.indexOf('featured') >= 0 || t.indexOf('product') >= 0 || t.indexOf('inventory') >= 0 || t.indexOf('vehicle') >= 0) {
                var grid = el.querySelector('[class*="grid"],[class*="products"],[class*="inventory"],.row,ul');
                return grid || el;
            }
        }
        return root;
    }

    function ensureStyle(){
        if (document.getElementById('ai-product-safety-style')) { return; }
        var style = document.createElement('style');
        style.id = 'ai-product-safety-style';
        style.textContent = ''
            + '.ai-product-safety-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin-top:12px}'
            + '.ai-product-safety-card{border:1px solid rgba(207,220,243,.45);border-radius:14px;overflow:hidden;background:rgba(12,20,36,.35);backdrop-filter:blur(2px)}'
            + '.ai-product-safety-media{height:150px;background:rgba(255,255,255,.04);display:grid;place-items:center}'
            + '.ai-product-safety-media img{width:100%;height:100%;object-fit:cover;display:block}'
            + '.ai-product-safety-body{padding:12px}'
            + '.ai-product-safety-title{margin:0 0 6px;font-size:16px;color:#fff}'
            + '.ai-product-safety-desc{margin:0 0 8px;font-size:12px;line-height:1.45;color:#b7c5de}'
            + '.ai-product-safety-desc p{margin:0 0 6px}'
            + '.ai-product-safety-desc ul,.ai-product-safety-desc ol{margin:0 0 6px 18px;padding:0}'
            + '.ai-product-safety-desc li{margin:0 0 3px}'
            + '.ai-product-safety-price{font-weight:700;color:#ffd06b;font-size:15px}'
            + '.ai-product-safety-actions{margin-top:8px}'
            + '.ai-product-safety-link{display:inline-block;padding:8px 12px;border-radius:999px;background:#e63946;color:#fff;text-decoration:none;font-size:12px;font-weight:700;box-shadow:0 6px 16px rgba(230,57,70,.25)}'
            + '.ai-product-safety-link:hover{background:#ff4d5a;color:#fff;text-decoration:none}';
        document.head.appendChild(style);
    }

    function renderProducts(root, data){
        var products = Array.isArray(data && data.products) ? data.products : [];
        if (!products.length || !root) { return; }
        if (root.getAttribute('data-ai-product-safety-rendered') === '1') { return; }

        ensureStyle();

        var cards = [];
        for (var i = 0; i < products.length; i++) {
            var p = products[i] || {};
            var name = text(p.product_name || p.name || p.title || ('Product ' + (i + 1)));
            var descHtml = safeRichHtml(p.product_description || p.description || p.body || '');
            var price = text(p.price || 'Price on request');
            var currency = text(p.currency || '');
            var imgs = pickImages(p);
            var img = imgs.length ? imgs[0] : '';
            var productUrl = text(p.product_url || p.url || p.link || '');

            cards.push(''
                + '<article class="ai-product-safety-card" data-product-card="1">'
                + '<div class="ai-product-safety-media">' + (img ? ('<img src="' + esc(img) + '" alt="' + esc(name) + '">') : '<span style="color:#9db0ce;font-size:12px">No image</span>') + '</div>'
                + '<div class="ai-product-safety-body">'
                + '<h3 class="ai-product-safety-title">' + esc(name || 'Product') + '</h3>'
                + (descHtml ? ('<div class="ai-product-safety-desc">' + descHtml + '</div>') : '')
                + '<div class="ai-product-safety-price">' + esc((currency ? currency + ' ' : '') + price) + '</div>'
                + (productUrl ? ('<div class="ai-product-safety-actions"><a class="ai-product-safety-link view-product-btn" href="' + esc(productUrl) + '" target="_blank" rel="noopener noreferrer">View product</a></div>') : '')
                + '</div>'
                + '</article>');
        }

        if (!cards.length) { return; }

        var container = pickContainer(root);
        if (!container) { container = root; }

        var old = container.querySelector('.ai-product-safety-grid');
        if (old) { old.remove(); }

        var wrap = document.createElement('div');
        wrap.className = 'ai-product-safety-grid';
        wrap.innerHTML = cards.join('');
        container.appendChild(wrap);

        var countBadge = document.getElementById('product-count-badge');
        if (countBadge) {
            countBadge.textContent = String(products.length) + ' plans';
        }

        root.setAttribute('data-ai-product-safety-rendered', '1');
    }

    function run(){
        if (!window.AI_STORE_DATA_URL) { return; }
        var root = rootEl();
        if (!root) { return; }
        if (hasProductCards(root) && !hasLoadingPlaceholder(root)) { return; }

        if (window.__AI_STORE_DATA__) {
            renderProducts(root, window.__AI_STORE_DATA__ || {});
            return;
        }

        fetch(window.AI_STORE_DATA_URL, {credentials:'same-origin'})
            .then(function(r){ return r.json(); })
            .then(function(data){
                var fresh = rootEl();
                if (!fresh) { return; }
                if (hasProductCards(fresh) && !hasLoadingPlaceholder(fresh)) { return; }
                renderProducts(fresh, data || {});
            })
            .catch(function(){});
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function(){
            setTimeout(run, 500);
            setTimeout(run, 1800);
        });
    } else {
        setTimeout(run, 500);
        setTimeout(run, 1800);
    }
})();
</script>
HTML;

        if (stripos($html, '</body>') !== false) {
            return str_ireplace('</body>', $script . "\n</body>", $html);
        }

        return $html . "\n" . $script;
    }

    private function hasAiStoreDataFetchCall(string $html): bool
    {
        $scan = (string) $html;

        if (! preg_match('/window\s*\.\s*AI_STORE_DATA_URL/i', $scan)) {
            return false;
        }

        // Direct fetch(window.AI_STORE_DATA_URL) or fetch(window['AI_STORE_DATA_URL']).
        if (preg_match('/fetch\s*\(\s*(window\s*\.\s*AI_STORE_DATA_URL|window\s*\[\s*["\']AI_STORE_DATA_URL["\']\s*\])/i', $scan)) {
            return true;
        }

        // Alias support: const DATA_URL = window.AI_STORE_DATA_URL; ... fetch(DATA_URL)
        if (preg_match('/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*window\s*\.\s*AI_STORE_DATA_URL[\s\S]{0,1200}?fetch\s*\(\s*\1\b/i', $scan)) {
            return true;
        }

        return false;
    }
}
