<?php

namespace App\Services;

class AiWebsiteService
{
    private DeepSeekService $deepSeek;

    private const REQUIRED_STYLE_ID = 'app-css';
    private const REQUIRED_SCRIPT_ID = 'app-js';

    public function __construct(DeepSeekService $deepSeek)
    {
        $this->deepSeek = $deepSeek;
    }

    /**
     * Cleanup stored generation JSON files to prevent unbounded growth.
     * Keeps the newest N files and removes the rest.
     */
    public function cleanupStoredGenerations(int $communityId, int $keepNewest = 20): void
    {
        if ($communityId <= 0) {
            return;
        }

        $dir = storage_path('app/ai-websites/'.$communityId);
        if (! is_dir($dir)) {
            return;
        }

        $files = glob($dir.DIRECTORY_SEPARATOR.'*.json') ?: [];
        if (count($files) <= $keepNewest) {
            return;
        }

        usort($files, function ($a, $b) {
            $at = @filemtime($a);
            $bt = @filemtime($b);
            $at = ($at === false) ? 0 : (int) $at;
            $bt = ($bt === false) ? 0 : (int) $bt;
            return $bt <=> $at;
        });

        $toDelete = array_slice($files, $keepNewest);
        foreach ($toDelete as $file) {
            if (is_file($file)) {
                @unlink($file);
            }
        }
    }

    /**
     * Generate a single-file website (HTML with inline <style> and <script>).
     * Keep output predictable so we can split it on deploy.
     */
    public function generateSingleFile(string $userRequest, string $currentHtml = ''): string
    {
        $isEdit = trim($currentHtml) !== '';
        $system = implode("\n", array_filter([
            $isEdit ? 'You update existing single-page websites.' : 'You generate complete single-page websites.',
            'Return ONLY a complete HTML document (no markdown, no backticks).',
            'Include EXACTLY one <style id="app-css"> block for all CSS.',
            'Include EXACTLY one <script id="app-js"> block for all JavaScript.',
			'Do NOT use React, JSX, TSX, or any framework syntax. Use plain browser JavaScript only.',
			'Never write HTML tags directly inside JavaScript unless they are inside a quoted string or a backtick template literal.',
			'When building HTML in JS (innerHTML), use template literals like: items.map(x => `...`).join(\'\') (no JSX, no unquoted <div>).',
            'Use px units for all font-size declarations in CSS (do not use rem for font-size).',
            'In header navigation, wrap the logo image in an anchor linking to index.html.',
            'When output is split into header/footer files, keep all mobile menubar code in header only; do not place mobile menubar markup in body sections.',
            'Add mobile menu HTML only inside header.html. Do not add mobile menu markup in index.html, footer.html, script.js, or dynamic.js. If mobile menu behavior needs JavaScript, keep only behavior handlers in script.js, but keep all mobile menu DOM markup in header.html only.',
            'Mobile menu JavaScript must run after header.html injection. Bind handlers on the aiwb:partials-ready event (or use delegated document-level handlers) so mobile toggle works on first load.',
            'Do not add global img display rules like img { display:block; }. If needed, apply display rules only to specific component classes/selectors.',
            'Include a global font protection rule using the same chosen primary font stack for that generated site (do not hardcode a fixed stack): *, body { font-family: [chosen-primary-font-stack] !important; }',
			'If you include images, use REAL publicly accessible HTTPS image URLs only (no relative paths like images/x.jpg, no local file paths).',
			'Prefer using https://source.unsplash.com/ or https://images.unsplash.com/ for placeholder/relevant images.',
            $isEdit ? 'Preserve the existing layout and content unless the change request requires modifying it.' : null,
            $isEdit ? 'Apply ONLY the requested changes and return the FULL updated HTML document.' : null,
            'Do not include explanations, reasoning, or analysis.',
			'Keep the HTML/CSS/JS concise while remaining complete.',
			'Avoid external dependencies unless absolutely necessary.',
			'Add the !important to each value of footer{ } in css',
			'Always add fixed position to header at top, means header part should not be scolled, it should be at top fixed position',
			'Always make the menubar menus attractive/interactive by changing text color on mouse hover with respective selected colors, dont make menubar menus like button on hover just change text color',
			'Change the background color and text color on mouse hover for buttons with respective selected colors',
			'Give the shadow on mouse hover to blocks types contens and images, event gallery main images and it should look nice like some animation on hover'
        ]));

        $userContent = trim($userRequest);
        if ($isEdit) {
            $userContent = implode("\n", [
                'CHANGE REQUEST:',
                trim($userRequest),
                '',
                'CURRENT HTML:',
                trim($currentHtml),
            ]);
        }

        $maxTokens = (int) config('deepseek.max_tokens');
        $options = [
            'temperature' => 0.2,
            'max_tokens' => $maxTokens,
            'request_reasoning' => false,
        ];

        $baseMessages = [
            ['role' => 'system', 'content' => $system],
            ['role' => 'user', 'content' => $userContent],
        ];

        $html = '';
        $lastError = '';
        for ($attempt = 1; $attempt <= 3; $attempt++) {
            try {
                if ($attempt === 1) {
                    $result = $this->deepSeek->chat($baseMessages, $options);
                } else {
                    $retryRules = [
                        'IMPORTANT: Your previous answer was cut off. Return ONE complete HTML document.',
                        'Be MUCH more concise so the whole HTML fits in one response.',
                        'Keep the HTML under 250 lines if possible.',
                        'Use short placeholder text (1 sentence per section).',
                        'Keep CSS minimal; avoid excessive styling variations.',
                        'Output ONLY the final HTML document (no markdown, no backticks).',
                    ];
                    $retryUser = $isEdit
                        ? implode("\n", array_merge([
                            'CHANGE REQUEST:',
                            trim($userRequest),
                            '',
                            'CURRENT HTML:',
                            trim($currentHtml),
                            '',
                        ], $retryRules))
                        : implode("\n", array_merge([
                            trim($userRequest),
                            '',
                        ], $retryRules));

                    $result = $this->deepSeek->chat([
                        ['role' => 'system', 'content' => $system],
                        ['role' => 'user', 'content' => $retryUser],
                    ], $options);
                }
            } catch (\Throwable $e) {
                $lastError = $e->getMessage();
                continue;
            }

            $html = $this->normalizeGeneratedHtml((string) ($result['content'] ?? ''));
            if (! $this->isLikelyTruncatedHtml($html)) {
                return $html;
            }
        }

        throw new \RuntimeException($lastError ?: 'AI response was cut off. Please shorten the request and try again.');
    }

    /**
     * DeepSeek sometimes wraps output in markdown code fences like:
     * ```html
     * ...
     * ```
     * This strips fences/backticks so preview/deploy don't show them.
     */
    public function normalizeGeneratedHtml(string $text): string
    {
        $text = trim((string) $text);
        if ($text === '') {
            return '';
        }

        // If the response contains a fenced code block, extract the first fenced block body.
        if (preg_match('/```[a-zA-Z0-9_-]*\s*([\s\S]*?)\s*```/m', $text, $m)) {
            $text = trim((string) ($m[1] ?? ''));
        }

        // Remove any remaining stray backticks.
        $text = preg_replace('/^`{1,3}/m', '', $text) ?? $text;
        $text = preg_replace('/`{1,3}$/m', '', $text) ?? $text;
        $text = trim($text);

        // Sometimes the language label "html" leaks onto its own line.
        $text = preg_replace('/^\s*html\s*\r?\n/i', '', $text, 1) ?? $text;

        // Guardrail: DeepSeek sometimes outputs JSX-like HTML inside JS (e.g. map(x => <div>...)
        // which breaks the page with: expected expression, got '<'. Convert these to template literals.
        $text = $this->fixJsxLikeHtmlInsideJs($text);

        // Guardrail: Sometimes it builds HTML with concatenation but forgets to quote the markup:
        // `html += <div ...>` (missing opening backtick/quote). This is also a JS parse error.
        $text = $this->fixUnquotedHtmlConcatsInsideJs($text);

        // Guardrail: Sometimes it returns raw HTML inside a function:
        // `return <div ...>` or `return` on one line and `<div ...>` on the next.
        $text = $this->fixUnquotedHtmlReturnsInsideJs($text);

        // Guardrail: Sometimes it injects literal <script ...></script> tags inside the JS itself,
        // which also breaks parsing with: expected expression, got '<'. Strip nested script tags.
        $text = $this->stripNestedScriptTagsInsideScripts($text);
        return trim($text);
    }

    private function fixJsxLikeHtmlInsideJs(string $html): string
    {
        if (stripos($html, '<script') === false || strpos($html, '.map(') === false) {
            return $html;
        }

        return preg_replace_callback('/<script\b[^>]*>([\s\S]*?)<\/script>/i', function ($m) {
            $script = (string) ($m[1] ?? '');
            if ($script === '') {
                return $m[0];
            }

            // Fix `.map(x => <div...` and `.map(x =>\n  <div...` by inserting an opening backtick.
            $script = preg_replace(
                '/(\.map\(\s*\(?\s*[\w$]+\s*\)?\s*=>\s*)</',
                '$1`<',
                $script
            ) ?? $script;
            $script = preg_replace(
                '/(\.map\(\s*\(?\s*[\w$]+\s*\)?\s*=>\s*\r?\n\s*)</',
                '$1`<',
                $script
            ) ?? $script;

            return str_replace($m[1], $script, $m[0]);
        }, $html) ?? $html;
    }

    private function stripNestedScriptTagsInsideScripts(string $html): string
    {
        if (stripos($html, '<script') === false) {
            return $html;
        }

        return preg_replace_callback('/<script\b[^>]*>([\s\S]*?)<\/script>/i', function ($m) {
            $script = (string) ($m[1] ?? '');
            if ($script === '') {
                return $m[0];
            }

            // Remove any nested script tags that would break JS parsing.
            $script = preg_replace('/<script\b[^>]*>[\s\S]*?<\/script>/i', '', $script) ?? $script;
            // Also remove any stray/unclosed script tokens just in case.
            $script = preg_replace('/<\/?script\b[^>]*>/i', '', $script) ?? $script;

            return str_replace($m[1], $script, $m[0]);
        }, $html) ?? $html;
    }

    private function fixUnquotedHtmlConcatsInsideJs(string $html): string
    {
        if (stripos($html, '<script') === false || strpos($html, '+=') === false) {
            return $html;
        }

        return preg_replace_callback('/<script\b[^>]*>([\s\S]*?)<\/script>/i', function ($m) {
            $script = (string) ($m[1] ?? '');
            if ($script === '') {
                return $m[0];
            }

            // Fix cases like:
            //   html += <div ...>
            //   html +=\n    <div ...>
            // by inserting an opening backtick.
            $script = preg_replace('/(\b[A-Za-z_$][\w$]*\s*\+=\s*)</m', '$1`<', $script) ?? $script;
            $script = preg_replace('/(\b[A-Za-z_$][\w$]*\s*\+=\s*\r?\n\s*)</m', '$1`<', $script) ?? $script;

            return str_replace($m[1], $script, $m[0]);
        }, $html) ?? $html;
    }

    private function fixUnquotedHtmlReturnsInsideJs(string $html): string
    {
        if (stripos($html, '<script') === false || stripos($html, 'return') === false) {
            return $html;
        }

        return preg_replace_callback('/<script\b[^>]*>([\s\S]*?)<\/script>/i', function ($m) {
            $script = (string) ($m[1] ?? '');
            if ($script === '') {
                return $m[0];
            }

            $lines = preg_split("/\r?\n/", $script);
            if (! is_array($lines) || count($lines) === 0) {
                return $m[0];
            }

            $inTemplate = false;
            $openIndent = '';
            $openTag = '';

            for ($i = 0; $i < count($lines); $i++) {
                $line = $lines[$i];

                if (! $inTemplate) {
                    // Case A: `return <div...`
                    if (preg_match('/^(\s*)return\s*(\(?\s*)</', $line, $mm)) {
                        $indent = (string) ($mm[1] ?? '');

                        // Insert opening backtick before first '<' after return.
                        $lines[$i] = preg_replace('/^(\s*return\s*\(?\s*)</', '$1`<', $line, 1) ?? $line;

                        // Capture the tag name from this same line.
                        if (preg_match('/`<\s*([a-zA-Z][\w:-]*)/i', $lines[$i], $tagM)) {
                            $openTag = (string) ($tagM[1] ?? '');
                            $openIndent = $indent;
                            $inTemplate = $openTag !== '';
                        }

                        continue;
                    }

                    // Case B: `return` on one line, `<div...` on the next.
                    if (preg_match('/^\s*return\s*(\(?\s*)$/', $line)) {
                        $shouldMergeReturn = preg_match('/^\s*return\s*$/', $line) === 1;

                        $j = $i + 1;
                        while ($j < count($lines) && trim((string) $lines[$j]) === '') {
                            $j++;
                        }
                        if ($j < count($lines) && preg_match('/^(\s*)</', (string) $lines[$j], $mm2)) {
                            $indent = (string) ($mm2[1] ?? '');
                            $next = (string) $lines[$j];

                            // Only if it isn't already quoted/backticked.
                            if (! preg_match('/^(\s*)[`\"\']/', $next)) {
                                $lines[$j] = preg_replace('/^(\s*)</', '$1`<', $next, 1) ?? $next;
                            }

                            // Prevent JS automatic semicolon insertion (`return` + newline). If the
                            // return is on its own line, merge the next template-literal line onto it.
                            if ($shouldMergeReturn) {
                                $lines[$i] = rtrim((string) $lines[$i]) . ' ' . ltrim((string) $lines[$j]);
                                for ($k = $i + 1; $k <= $j; $k++) {
                                    $lines[$k] = '';
                                }
                            }

                            if (preg_match('/`<\s*([a-zA-Z][\w:-]*)/i', (string) $lines[$j], $tagM2)) {
                                $openTag = (string) ($tagM2[1] ?? '');
                                $openIndent = $indent;
                                $inTemplate = $openTag !== '';
                            }
                        }
                    }

                    continue;
                }

                // Close the template literal at the matching closing tag with the same indent.
                $trim = trim((string) $line);
                if ($openTag !== '' && str_starts_with((string) $line, $openIndent) && preg_match('/^<\/\s*'.preg_quote($openTag, '/').'\s*>\s*$/i', $trim)) {
                    if (strpos($line, '`') === false) {
                        $lines[$i] = rtrim((string) $line) . '`';
                    }
                    $inTemplate = false;
                    $openIndent = '';
                    $openTag = '';
                }
            }

            $scriptFixed = implode("\n", $lines);
            return str_replace($m[1], $scriptFixed, $m[0]);
        }, $html) ?? $html;
    }

    private function isLikelyTruncatedHtml(string $html): bool
    {
        $trimmed = trim($html);
        if ($trimmed === '') {
            return true;
        }

        // Must look like a full document and end properly.
        if (stripos($trimmed, '<!doctype') === false && stripos($trimmed, '<html') === false) {
            return true;
        }
        if (stripos($trimmed, '</html>') === false) {
            return true;
        }

        // Ensure required blocks are present (these are needed for deploy splitting).
        if (stripos($trimmed, '<style') === false || stripos($trimmed, 'id="'.self::REQUIRED_STYLE_ID.'"') === false) {
            return true;
        }
        if (stripos($trimmed, '<script') === false || stripos($trimmed, 'id="'.self::REQUIRED_SCRIPT_ID.'"') === false) {
            return true;
        }

        // Basic closing tags.
        if (stripos($trimmed, '</body>') === false) {
            return true;
        }

        return false;
    }

    /**
    * Deploy by splitting a single-file HTML into:
    *  - index.html
    *  - header.html
    *  - footer.html
    *  - style.css
    *  - script.js
    *  - dynamic.css
    *  - dynamic.js
     */
    public function deploySplitFiles(int $communityId, string $html): array
    {
        if ($communityId <= 0) {
            throw new \InvalidArgumentException('Invalid community id.');
        }

		$html = $this->normalizeGeneratedHtml($html);

        $root = public_path('c/'.$communityId.'/web');
        if (! is_dir($root) && ! @mkdir($root, 0775, true) && ! is_dir($root)) {
            throw new \RuntimeException('Failed to create deploy directory.');
        }

        // Keep output STRICTLY under /public/c/{id}/web (index.php + css + js only).
        // If an older auto-generated /public/c/{id}/index.php exists from a previous deploy, remove it.
        $this->removeAutoGeneratedCommunityIndex($communityId);

        [$css, $htmlWithoutCss] = $this->extractAndRemoveStyle($html);
        $css = $this->removeGlobalImgDisplayRules($css);
        $css = $this->appendPriorityHeaderOverrides($css);
        [$js, $htmlWithoutCssJs] = $this->extractAndRemoveScript($htmlWithoutCss);

        [$htmlFinal, $headerMarkup, $footerMarkup] = $this->injectAssets($htmlWithoutCssJs);
        $htmlFinal = $this->stripAutoGeneratedThemeGuardFromIndexHtml($htmlFinal);

        $indexPath = $root.DIRECTORY_SEPARATOR.'index.html';
        $headerPath = $root.DIRECTORY_SEPARATOR.'header.html';
        $footerPath = $root.DIRECTORY_SEPARATOR.'footer.html';
        $cssPath = $root.DIRECTORY_SEPARATOR.'style.css';
        $jsPath = $root.DIRECTORY_SEPARATOR.'script.js';
        $dynamicCssPath = $root.DIRECTORY_SEPARATOR.'dynamic.css';
        $dynamicJsPath = $root.DIRECTORY_SEPARATOR.'dynamic.js';

        $scriptFile = $this->buildScriptFileContent($js);
        $dynamicCss = $this->buildDynamicCssFileContent();
        $dynamicJs = $this->buildDynamicJsFileContent($communityId);

        file_put_contents($headerPath, trim($headerMarkup) !== '' ? trim($headerMarkup)."\n" : '');
        file_put_contents($footerPath, trim($footerMarkup) !== '' ? trim($footerMarkup)."\n" : '');
        file_put_contents($cssPath, $css);
        file_put_contents($jsPath, $scriptFile);
        file_put_contents($dynamicCssPath, $dynamicCss);
        file_put_contents($dynamicJsPath, $dynamicJs);
        file_put_contents($indexPath, $htmlFinal);

        // Canonical redirect is handled by Laravel route/controller.
        // Do not keep per-community .htaccess in /web.
        $legacyHtaccess = $root.DIRECTORY_SEPARATOR.'.htaccess';
        if (is_file($legacyHtaccess)) {
            @unlink($legacyHtaccess);
        }

        return [
            'paths' => [
                'index' => $indexPath,
                'header' => $headerPath,
                'footer' => $footerPath,
                'css' => $cssPath,
                'js' => $jsPath,
                'dynamic_css' => $dynamicCssPath,
                'dynamic_js' => $dynamicJsPath,
            ],
            'urls' => [
                'index' => url('c/'.$communityId.'/web/'),
                'index_html' => url('c/'.$communityId.'/web/index.html'),
                'header' => url('c/'.$communityId.'/web/header.html'),
                'footer' => url('c/'.$communityId.'/web/footer.html'),
                'css' => url('c/'.$communityId.'/web/style.css'),
                'js' => url('c/'.$communityId.'/web/script.js'),
                'dynamic_css' => url('c/'.$communityId.'/web/dynamic.css'),
                'dynamic_js' => url('c/'.$communityId.'/web/dynamic.js'),
            ],
        ];
    }

    private function stripAutoGeneratedThemeGuardFromIndexHtml(string $html): string
    {
        // Older deployments prepended a PHP guard. We now enforce this via route/controller,
        // so keep the deployed index file as pure HTML.
        if (strpos($html, 'Auto-generated guard by AI website deploy') === false) {
            return $html;
        }

        $ltrimmed = ltrim($html);
        if (stripos($ltrimmed, '<?php') !== 0) {
            return $html;
        }

        $end = strpos($html, '?>');
        if ($end === false) {
            return $html;
        }

        return ltrim(substr($html, $end + 2));
    }

    /**
     * Ensure public/c/{id}/index.php exists if the directory is already present.
     * This avoids creating new directories for communities that haven't deployed.
     */
    public function ensureCommunityDirIndexIfExists(int $communityId): void
    {
        if ($communityId <= 0) {
            return;
        }
        $this->ensureCommunityDirIndex($communityId);
    }

    private function removeAutoGeneratedCommunityIndex(int $communityId): void
    {
        if ($communityId <= 0) {
            return;
        }

        $communityDir = public_path('c/'.$communityId);
        if (! is_dir($communityDir)) {
            return;
        }

        $indexPath = $communityDir.DIRECTORY_SEPARATOR.'index.php';
        if (! is_file($indexPath)) {
            return;
        }

        // Only remove if it matches our previously auto-generated forwarder.
        $contents = '';
        try {
            $contents = (string) @file_get_contents($indexPath);
        } catch (\Throwable $e) {
            $contents = '';
        }
        if (strpos($contents, 'Auto-generated by AI website deploy') === false) {
            return;
        }

        @unlink($indexPath);
    }

    private function ensureCommunityDirIndex(int $communityId): void
    {
        $communityDir = public_path('c/'.$communityId);
        if (! is_dir($communityDir)) {
            return;
        }

        $indexPath = $communityDir.DIRECTORY_SEPARATOR.'index.php';
        if (is_file($indexPath)) {
            return;
        }

        $publicIndex = realpath(public_path('index.php'));
        if (! $publicIndex || ! is_file($publicIndex)) {
            return;
        }

        $code = implode("\n", [
            '<?php',
            '// Auto-generated by AI website deploy.',
            '// Forwards /c/{id}/ directory requests into Laravel so Apache does not 403.',
            '$publicDir = realpath(__DIR__ . DIRECTORY_SEPARATOR . ".." . DIRECTORY_SEPARATOR . "..");',
            'if (! $publicDir) { http_response_code(500); exit("Misconfigured"); }',
            '@chdir($publicDir);',
            '$_SERVER["SCRIPT_NAME"] = "/index.php";',
            '$_SERVER["PHP_SELF"] = "/index.php";',
            '$_SERVER["SCRIPT_FILENAME"] = $publicDir . DIRECTORY_SEPARATOR . "index.php";',
			'if (empty($_SERVER["REQUEST_URI"])) { $_SERVER["REQUEST_URI"] = "/c/'.(int) $communityId.'/"; }',
            'require $publicDir . DIRECTORY_SEPARATOR . "index.php";',
        ]);

        @file_put_contents($indexPath, $code);
    }

    private function extractAndRemoveStyle(string $html): array
    {
        $css = '';
        $pattern = '#<style\b[^>]*>(.*?)</style>#si';
        if (preg_match_all($pattern, $html, $matches)) {
            foreach ($matches[1] as $block) {
                $css .= trim($block)."\n\n";
            }
        }
        $html = preg_replace($pattern, '', $html) ?? $html;

        return [trim($css)."\n", $html];
    }

    private function extractAndRemoveScript(string $html): array
    {
        $js = '';
        // Only extract inline scripts (ignore <script src="...">)
        $pattern = '#<script\b(?![^>]*\bsrc=)[^>]*>(.*?)</script>#si';
        if (preg_match_all($pattern, $html, $matches)) {
            foreach ($matches[1] as $block) {
                $js .= trim($block)."\n\n";
            }
        }
        $html = preg_replace($pattern, '', $html) ?? $html;

        return [trim($js)."\n", $html];
    }

    private function injectAssets(string $html): array
    {
        $headerSlot = '<div id="aiwb-header-slot"></div>';
        $footerSlot = '<div id="aiwb-footer-slot"></div>';

        // Remove previous include placeholders (raw or HTML-encoded) before re-splitting.
        $html = preg_replace('/<\?php\s+include\s+__DIR__\s*\.\s*["\']\/(header|footer)\.php["\']\s*;\s*\?>/i', '', $html) ?? $html;
        $html = preg_replace('/<\?php\s+include\s+__DIR__\s*\.\s*["\']\/(header|footer)\.html["\']\s*;\s*\?>/i', '', $html) ?? $html;
        $html = preg_replace('/&lt;\?php\s+include\s+__DIR__\s*\.\s*["\']\/(header|footer)\.(php|html)["\']\s*;\s*\?&gt;/i', '', $html) ?? $html;
        $html = str_replace(['<div id="aiwb-header-slot"></div>', '<div id="aiwb-footer-slot"></div>'], '', $html);

        $headerMarkup = '';
        if (preg_match('/<header\b[^>]*>[\s\S]*?<\/header>/i', $html, $m)) {
            $headerMarkup = (string) ($m[0] ?? '');
            $headerMarkup = $this->normalizeSamePageAnchorsForDeployedPages($headerMarkup);
            $html = preg_replace('/<header\b[^>]*>[\s\S]*?<\/header>/i', $headerSlot, $html, 1) ?? $html;
        }

        $footerMarkup = '';
        if (preg_match_all('/<footer\b[^>]*>[\s\S]*?<\/footer>/i', $html, $matches) && ! empty($matches[0])) {
            $footerMarkup = (string) end($matches[0]);
            $footerMarkup = $this->normalizeSamePageAnchorsForDeployedPages($footerMarkup);
            $html = preg_replace('/<footer\b[^>]*>[\s\S]*?<\/footer>/i', $footerSlot, $html, 1) ?? $html;
        }

        if ($headerMarkup === '') {
            if (stripos($html, '<body') !== false) {
                $html = preg_replace('/<body\b[^>]*>/i', '$0' . "\n" . $headerSlot, $html, 1) ?? $html;
            } else {
                $html = $headerSlot . "\n" . $html;
            }
        }

        if ($footerMarkup === '') {
            if (stripos($html, '</body>') !== false) {
                $html = preg_replace('#</body>#i', $footerSlot . "\n</body>", $html, 1) ?? $html;
            } else {
                $html .= "\n" . $footerSlot . "\n";
            }
        }

        $headAssets = '<link rel="stylesheet" href="style.css">' . "\n"
            . '<link rel="stylesheet" href="dynamic.css">';

        if (stripos($html, '</head>') !== false) {
            $html = preg_replace('#</head>#i', $headAssets . "\n</head>", $html, 1) ?? $html;
        } else {
            $html = $headAssets . "\n" . $html;
        }

        $bodyScripts = '<script src="script.js" defer></script>' . "\n"
            . '<script src="dynamic.js" defer></script>';

        if (stripos($html, '</body>') !== false) {
            $html = preg_replace('#</body>#i', $bodyScripts . "\n</body>", $html, 1) ?? $html;
        } else {
            $html .= "\n" . $bodyScripts . "\n";
        }

        return [$html, $headerMarkup, $footerMarkup];
    }

    private function normalizeSamePageAnchorsForDeployedPages(string $markup): string
    {
        if (trim($markup) === '') {
            return $markup;
        }

        // Keep same-page menu links explicit for deployed pages.
        // Example: href="#aboutus" -> href="index.html#aboutus"
        return preg_replace('/\bhref\s*=\s*(["\'])#([^"\'\s>]+)\1/i', 'href=$1index.html#$2$1', $markup) ?? $markup;
    }

    private function buildScriptFileContent(string $js): string
    {
        $loaderJs = <<<'JS'
(function () {
    var pendingPartials = 2;

    function markPartialDone() {
        pendingPartials -= 1;
        if (pendingPartials <= 0) {
            try {
                document.dispatchEvent(new CustomEvent('aiwb:partials-ready'));
            } catch (e) {
                var evt = document.createEvent('Event');
                evt.initEvent('aiwb:partials-ready', true, true);
                document.dispatchEvent(evt);
            }
        }
    }

    function bindMobileMenuFallback() {
        var btn = document.querySelector('#mobileMenuBtn, .mobile-menu-toggle, [data-mobile-menu-toggle]');
        var panel = document.querySelector('#mobileMenu, .mobile-menu-panel, [data-mobile-menu]');
        var overlay = document.querySelector('#mobileOverlay, .mobile-overlay, [data-mobile-overlay]');

        if (!btn || !panel) {
            return;
        }
        if (btn.getAttribute('data-aiwb-menu-bound') === '1') {
            return;
        }

        function isOpen() {
            return panel.classList.contains('open') || panel.classList.contains('active');
        }

        function openMenu() {
            panel.classList.add('open');
            panel.classList.add('active');
            panel.style.display = 'block';
            btn.setAttribute('aria-expanded', 'true');
            if (overlay) {
                overlay.classList.add('open');
                overlay.classList.add('active');
                overlay.style.display = 'block';
            }
        }

        function closeMenu() {
            panel.classList.remove('open');
            panel.classList.remove('active');
            panel.style.display = '';
            btn.setAttribute('aria-expanded', 'false');
            if (overlay) {
                overlay.classList.remove('open');
                overlay.classList.remove('active');
                overlay.style.display = '';
            }
        }

        btn.addEventListener('click', function (e) {
            e.preventDefault();
            if (isOpen()) {
                closeMenu();
            } else {
                openMenu();
            }
        });

        if (overlay) {
            overlay.addEventListener('click', function () {
                closeMenu();
            });
        }

        var links = panel.querySelectorAll('a[href]');
        for (var i = 0; i < links.length; i++) {
            links[i].addEventListener('click', function () {
                closeMenu();
            });
        }

        btn.setAttribute('data-aiwb-menu-bound', '1');
    }

    function removeSlot(slotId) {
        var slot = document.getElementById(slotId);
        if (slot) {
            slot.remove();
        }
    }

    function ensureStylesFrom(fragment) {
        var links = fragment.querySelectorAll('link[rel="stylesheet"][href]');
        for (var i = 0; i < links.length; i++) {
            var href = links[i].getAttribute('href');
            if (!href) {
                continue;
            }
            if (document.querySelector('link[data-aiwb-asset="' + href + '"]')) {
                continue;
            }
            var l = document.createElement('link');
            l.rel = 'stylesheet';
            l.href = href;
            l.setAttribute('data-aiwb-asset', href);
            document.head.appendChild(l);
        }
    }

    function ensureScriptsFrom(fragment) {
        var scripts = fragment.querySelectorAll('script[src]');
        for (var i = 0; i < scripts.length; i++) {
            var src = scripts[i].getAttribute('src');
            if (!src) {
                continue;
            }
            if (document.querySelector('script[data-aiwb-asset="' + src + '"]')) {
                continue;
            }
            var s = document.createElement('script');
            s.src = src;
            if (scripts[i].defer) {
                s.defer = true;
            }
            s.setAttribute('data-aiwb-asset', src);
            document.body.appendChild(s);
        }
    }

    function stripAssetTags(fragment) {
        var assetTags = fragment.querySelectorAll('link[rel="stylesheet"][href],script[src]');
        for (var i = 0; i < assetTags.length; i++) {
            assetTags[i].remove();
        }
    }
    
    function hydrateActionIcons(root) {
    if (!root) {
        return;
    }
    var maps = [
        { selector: "i.ion-thumbsup", symbol: "👍", label: "Like" },
        { selector: "i.ion-chatbox", symbol: "💬", label: "Comment" },
        { selector: "i.ion-forward", symbol: "↗", label: "Share" },
        { selector: "i.fa.fa-map-marker", symbol: "📍", label: "Location" }
    ];
    for (var m = 0; m < maps.length; m++) {
        var cfg = maps[m];
        var nodes = root.querySelectorAll(cfg.selector);
        for (var i = 0; i < nodes.length; i++) {
            var el = nodes[i];
            if (el.getAttribute("data-fallback") === "1") {
                continue;
            }
            if (!String(el.textContent || "").trim()) {
                el.textContent = cfg.symbol;
            }
            el.setAttribute("aria-label", cfg.label);
            el.style.fontSize = "15px";
            el.style.lineHeight = "1";
            el.style.color = "#606060";
            el.setAttribute("data-fallback", "1");
        }
    }
}

    function hydrateAllDynamicContainers() {
        var containers = document.querySelectorAll(
            '.community-latest-dynamic-blogs-container, ' +
            '.community-latest-dynamic-events-container, ' +
            '.community-latest-dynamic-crowdfunding-container, ' +
            '.community-latest-dynamic-storefront-container'
        );
        for (var i = 0; i < containers.length; i++) {
            hydrateActionIcons(containers[i]);
        }
    }

    function injectPartial(slotId, fileName) {
        var slot = document.getElementById(slotId);
        if (!slot) {
            markPartialDone();
            return;
        }

        fetch(fileName, { cache: 'no-store' })
            .then(function (res) {
                if (!res.ok) {
                    throw new Error('Failed to load ' + fileName);
                }
                return res.text();
            })
            .then(function (html) {
                var wrapper = document.createElement('div');
                wrapper.innerHTML = String(html || '');
                ensureStylesFrom(wrapper);
                ensureScriptsFrom(wrapper);
                stripAssetTags(wrapper);

                var current = document.getElementById(slotId);
                if (!current) {
                    markPartialDone();
                    return;
                }
                current.outerHTML = wrapper.innerHTML;
                bindMobileMenuFallback();
                try {
                    document.dispatchEvent(new CustomEvent('aiwb:partial-injected', { detail: { file: fileName } }));
                } catch (e) {
                    // no-op
                }
                markPartialDone();
            })
            .catch(function () {
                removeSlot(slotId);
                markPartialDone();
            });
    }

    function init() {
        bindMobileMenuFallback();
        injectPartial('aiwb-header-slot', 'header.html');
        injectPartial('aiwb-footer-slot', 'footer.html');
        setTimeout(hydrateAllDynamicContainers, 100);
    }
    
    document.addEventListener('aiwb:partial-injected', function() {
    	setTimeout(hydrateAllDynamicContainers, 100);
	});

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();
JS;

        $customJs = trim($js);
        if ($customJs !== '') {
            return $loaderJs . "\n\n" . $customJs . "\n";
        }

        return $loaderJs . "\n";
    }

    private function buildDynamicCssFileContent(): string
    {
        return implode("\n", [
            'footer {',
            '  text-align: center !important;',
            '  margin-top: auto !important;',
            '  padding: 8px !important;',
            '}',
            '',
            'footer .container,',
            'footer .footer-links,',
            'footer .footer-links li,',
            'footer .footer-links a {',
            '  text-align: center !important;',
            '}',
            '',
            'footer a,',
            'footer a:hover,',
            'footer a:focus {',
            '  text-decoration: none !important;',
            '}',
            '',
            '.community-latest-dynamic-blogs-container,',
            '.community-latest-dynamic-events-container,',
            '.community-latest-dynamic-crowdfunding-container,',
            '.community-latest-dynamic-storefront-container {',
            '  text-align: left !important;',
            '  background: transparent !important;',
            '  border: 0 !important;',
            '  padding: 0 !important;',
            '  width: 100%;',
            '}',
            '',
            '.community-latest-dynamic-blogs-container.dynamic-placeholder,',
            '.community-latest-dynamic-events-container.dynamic-placeholder,',
            '.community-latest-dynamic-crowdfunding-container.dynamic-placeholder,',
            '.community-latest-dynamic-storefront-container.dynamic-placeholder {',
            '  background: transparent !important;',
            '  border: 0 !important;',
            '  box-shadow: none !important;',
            '  border-radius: 0 !important;',
            '}',
            '',
            '#post-list.dynamic-blogs-posts,',
            '.business-products-section {',
            '  width: 100%;',
            '}',
            '',
            '.dynamic-blogs-posts .columns {',
            '  display: flex;',
            '  flex-wrap: wrap;',
            '  margin-left: -10px;',
            '  margin-right: -10px;',
            '}',
            '',
            '.dynamic-blogs-posts .column {',
            '  box-sizing: border-box;',
            '  padding: 10px;',
            '  width: 33.3333%;',
            '}',
            '',
            '.dynamic-blogs-posts .post-blox-view-box {',
            '  background: #fff;',
            '  border: 1px solid #e7e7e7;',
            '  border-radius: 8px;',
            '  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);',
            '  overflow: hidden;',
            '  height: 100%;',
            '  transition: transform 0.25s ease, box-shadow 0.25s ease;',
            '}',
            '',
            '.dynamic-blogs-posts .post-blox-view-box:hover,',
            '.business-product-card:hover {',
            '  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);',
            '  transform: scale(1.02);',
            '}',
            '',
            '.dynamic-blogs-posts .blog-post-image {',
            '  height: 180px;',
            '  background: #f6f6f6;',
            '  overflow: hidden;',
            '}',
            '',
            '.dynamic-blogs-posts .blog-post-image img,',
            '.dynamic-blogs-posts .blog-post-image iframe,',
            '.dynamic-blogs-posts .blog-post-image .upload-video,',
            '.dynamic-blogs-posts .blog-post-image video {',
            '  display: block;',
            '  width: 100% !important;',
            '  height: 100% !important;',
            '  max-height: 180px !important;',
            '  object-fit: cover;',
            '  border: 0 !important;',
            '  background: #000;',
            '}',
            '',
            '.dynamic-blogs-posts .blog-date-actns {',
            '  display: flex;',
            '  align-items: center;',
            '  justify-content: space-between;',
            '  gap: 8px;', 
            '  padding: 10px;',
            '}',
            '',
            '.blog-hash-name {',
            '  background: #c4171f;',
            '  color: #fff;',
            '  padding: 9px 12px;',
            '  font-weight: 700;',
            '  font-size: 14px;',
            '  text-transform: uppercase;',
            '}',
            '',
            '.postdata .post-texts {',
            '  padding: 10px;',
            '}',
            '',
            '.dynamic-blogs-posts .post-texts p {',
            '  margin: 0 0 8px 0;',
            '}',
            '.dynamic-blogs-posts .post-texts p:last-child {',
            '  margin-bottom: 0;',
            '}',
            '',
            '.dynamic-blogs-posts .posteventdata .eve-by,',
            '.dynamic-blogs-posts .posteventdata .eve-date {',
            '  font-weight: 700;',
            '}',
            '',
            '.eve-location .fa-map-marker::before {',
            '  content: none;',
            '}',
            '',
            '.dynamic-blogs-posts .blog-date-actns .post-actions-cnt {',
            '  display: inline-flex;',
            '  align-items: center;',
            '  gap: 6px;',
            '}',
            '',
            '.dynamic-blogs-posts .blog-date-actns .post-actions-cnt i {',
            '  display: inline-flex;',
            '  align-items: center;',
            '  justify-content: center;',
            '  width: 16px;',
            '  min-width: 16px;',
            '  font-style: normal;',
            '  font-size: 12px;',
            '  line-height: 1;',
            '  color: #666;',
            '}',
            '',
            '.dynamic-blogs-posts a {',
            '  text-decoration: none !important;',
            '  color: inherit;',
            '}',
            '',
            '.aiwb-dyn-loadmore-wrap {',
            '  text-align: center;',
            '  margin-top: 16px;',
            '}',
            '',
            '.aiwb-dyn-loadmore-btn {',
            '  box-shadow: 0 6px 16px rgba(196, 23, 31, 0.28);',
            '  background: #c4171f;',
            '  color: #fff;',
            '  border: 1px solid #c4171f;',
            '  border-radius: 999px;',
            '  padding: 10px 24px;',
            '  font-weight: 700;',
            '  letter-spacing: .2px;',
            '  cursor: pointer;',
            '  transition: all .2s ease;',
            '}',
            '',
            '.aiwb-dyn-loadmore-btn:disabled {',
            '  opacity: 0.75;',
            '  cursor: not-allowed;',
            '}',
            '',
            '@media (max-width: 1023px) {',
            '  .dynamic-blogs-posts .column {',
            '    width: 50%;',
            '  }',
            '}',
            '',
            '@media (max-width: 767px) {',
            '  .dynamic-blogs-posts .column {',
            '    width: 100%;',
            '  }',
            '}',
            '',
            '.aiwb-dyn-loading {',
            '  text-align: center;',
            '  padding: 10px;',
            '}',
            '',
        ]);
    }
    
    private function buildDynamicJsFileContent($communityId): string
    {
    	$communityBaseUrl= url('/');
        
        return implode("\n", [
            '(function () {',
            '  var COMMUNITY_ID = ' . intval($communityId) . ';',
            '  if (!(COMMUNITY_ID > 0)) {',
            '    return;',
            '  }',
            '',
           '  var FEED_URL = "' . $communityBaseUrl . '/cm/ai-website/dynamic-feed?community_id=" + encodeURIComponent(String(COMMUNITY_ID));',
            '  var TYPES = [',
            '    { key: "blogs", selector: ".community-latest-dynamic-blogs-container" },',
            '    { key: "events", selector: ".community-latest-dynamic-events-container" },',
            '    { key: "crowdfunding", selector: ".community-latest-dynamic-crowdfunding-container" },',
            '    { key: "storefront", selector: ".community-latest-dynamic-storefront-container" }',
            '  ];',
            '',
            '  function buildUrl(type, page) {',
            '    var sep = FEED_URL.indexOf("?") === -1 ? "?" : "&";',
            '    return FEED_URL + sep + "type=" + encodeURIComponent(type) + "&page=" + encodeURIComponent(String(page));',
            '  }',
            '',
            '  function hideClosest(node) {',
            '    if (!node) {',
            '      return;',
            '    }',
            '    var n = node;',
            '    while (n && n !== document.body) {',
            '      var cls = (typeof n.className === "string") ? n.className : "";',
            '      if ((n.tagName && n.tagName.toUpperCase() === "SECTION") || /(^|\\s)section(\\s|$)/i.test(cls) || /(^|\\s)ok-is-block(\\s|$)/i.test(cls)) {',
            '        n.style.display = "none";',
            '        return;',
            '      }',
            '      n = n.parentElement;',
            '    }',
            '    node.style.display = "none";',
            '  }',
            '',
            '  function hydrateImages(root) {',
            '    if (!root) {',
            '      return;',
            '    }',
            '    var imgs = root.querySelectorAll("img[data-original], img.lazy");',
            '    for (var i = 0; i < imgs.length; i++) {',
            '      var img = imgs[i];',
            '      var dataSrc = img.getAttribute("data-original") || "";',
            '      var src = img.getAttribute("src") || "";',
            '      if (!src && dataSrc) {',
            '        img.setAttribute("src", dataSrc);',
            '      }',
            '      if (!img.getAttribute("loading")) {',
            '        img.setAttribute("loading", "lazy");',
            '      }',
            '    }',
            '  }',
            '',
            '  function hydrateLoadVideos(root) {',
            '    if (!root) {',
            '      return;',
            '    }',
            '    var videos = root.querySelectorAll(".load-video[data-url]");',
            '    for (var i = 0; i < videos.length; i++) {',
            '      var holder = videos[i];',
            '      if (holder.getAttribute("data-video-loaded") === "1") {',
            '        continue;',
            '      }',
            '      var url = holder.getAttribute("data-url") || "";',
            '      if (!url) {',
            '        continue;',
            '      }',
            '      var iframe = document.createElement("iframe");',
            '      iframe.className = "upload-video";',
            '      iframe.setAttribute("allowfullscreen", "true");',
            '      iframe.setAttribute("webkitallowfullscreen", "true");',
            '      iframe.setAttribute("mozallowfullscreen", "true");',
            '      iframe.setAttribute("frameborder", "0");',
            '      iframe.setAttribute("scrolling", "no");',
            '      iframe.setAttribute("loading", "lazy");',
            '      iframe.setAttribute("src", url);',
            '      holder.innerHTML = "";',
            '      holder.appendChild(iframe);',
            '      holder.setAttribute("data-video-loaded", "1");',
            '    }',
            '  }',
            '',
            '  function hydrateActionIcons(root) {',
            '    if (!root) {',
            '      return;',
            '    }',
            '    var maps = [',
            '      { selector: "i.ion-thumbsup", label: "👍", aria: "Like" },',
            '      { selector: "i.ion-chatbox", label: "💬", aria: "Comment" },',
            '      { selector: "i.ion-forward", label: "↗", aria: "Share" },',
            '      { selector: "i.fa.fa-map-marker", label: "📍", aria: "Location" }',
            '    ];',
            '    for (var m = 0; m < maps.length; m++) {',
            '      var cfg = maps[m];',
            '      var nodes = root.querySelectorAll(cfg.selector);',
            '      for (var i = 0; i < nodes.length; i++) {',
            '        var el = nodes[i];',
            '        if (el.getAttribute("data-fallback") === "1") {',
            '          continue;',
            '        }',
            '        if (!String(el.textContent || "").trim()) {',
            '          el.textContent = cfg.label;',
            '        }',
            '        el.setAttribute("aria-label", cfg.aria);',
            '        el.style.fontSize = "15px";',
            '        el.style.lineHeight = "1";',
            '        el.style.color = "#606060";',
            '        el.setAttribute("data-fallback", "1");',
            '      }',
            '    }',
            '  }',
            '',
            '  function hydrateLocationIcons(root) {',
            '    if (!root) {',
            '      return;',
            '    }',
            '    var icons = root.querySelectorAll("i.fa.fa-map-marker");',
            '    for (var i = 0; i < icons.length; i++) {',
            '      var el = icons[i];',
            '      if (el.getAttribute("data-fallback") === "1") {',
            '        continue;',
            '      }',
            '      if (!String(el.textContent || "").trim()) {',
            '        el.textContent = "📍";',
            '      }',
            '      el.setAttribute("aria-label", "Location");',
            '      el.style.display = "inline-block";',
            '      el.style.width = "14px";',
            '      el.style.marginRight = "4px";',
            '      el.style.fontStyle = "normal";',
            '      el.style.lineHeight = "1";',
            '      el.style.color = "#c4171f";',
            '      el.setAttribute("data-fallback", "1");',
            '    }',
            '  }',
            '',
            '  function hydrateDynamicRoot(root) {',
            '    hydrateImages(root);',
            '    hydrateLoadVideos(root);',
            '    hydrateActionIcons(root);',
            '    hydrateLocationIcons(root);',
            '  }',
            '',
            '  var postToken = Math.random().toString(36).substring(2) + Date.now();',
            '  function appendPostTokenToLinks(root) {',
            '    var links = (root || document).querySelectorAll("a[href*=\"/post/\"]");',
            '    for (var i = 0; i < links.length; i++) {',
            '      var a = links[i];',
            '      var href = a.getAttribute("href") || "";',
            '      if (!href || href.indexOf("token=") !== -1 || href.indexOf("tkn=") !== -1) {',
            '        continue;',
            '      }',
            '      var sep = href.indexOf("?") > -1 ? "&" : "?";',
            '      a.setAttribute("href", href + sep + "tkn=" + encodeURIComponent(postToken));',
            '    }',
            '  }',
            '',
            '  function appendCards(container, html) {',
            '    if (!container || !html) {',
            '      return;',
            '    }',
            '    var temp = document.createElement("div");',
            '    temp.innerHTML = String(html);',
            '    var srcCols = temp.querySelector(".columns");',
            '    var dstCols = container.querySelector(".columns");',
            '    if (srcCols && dstCols) {',
            '      while (srcCols.firstChild) {',
            '        dstCols.appendChild(srcCols.firstChild);',
            '      }',
            '      hydrateDynamicRoot(dstCols);',
            '      appendPostTokenToLinks(dstCols);',
            '      return;',
            '    }',
            '    container.insertAdjacentHTML("beforeend", String(html));',
            '    hydrateDynamicRoot(container);',
            '    appendPostTokenToLinks(container);',
            '  }',
            '',
            '  function mountLoadMore(node, type, startPage) {',
            '    if (!node) {',
            '      return;',
            '    }',
            '    var existing = node.parentNode ? node.parentNode.querySelector(".aiwb-dyn-loadmore-wrap[data-type=\"" + type + "\"]") : null;',
            '    if (existing && existing.parentNode) {',
            '      existing.parentNode.removeChild(existing);',
            '    }',
            '    var wrap = document.createElement("div");',
            '    wrap.className = "aiwb-dyn-loadmore-wrap";',
            '    wrap.setAttribute("data-type", type);',
            '    var btn = document.createElement("button");',
            '    btn.type = "button";',
            '    btn.className = "btn btn-primary aiwb-dyn-loadmore-btn";',
            '    btn.textContent = "Load More";',
            '    wrap.appendChild(btn);',
            '    node.insertAdjacentElement("afterend", wrap);',
            '',
            '    var page = startPage;',
            '    var loading = false;',
            '    btn.addEventListener("click", function () {',
            '      if (loading) {',
            '        return;',
            '      }',
            '      loading = true;',
            '      btn.disabled = true;',
            '      btn.style.opacity = ".8";',
            '      btn.textContent = "Loading...";',
            '',
            '      fetch(buildUrl(type, page + 1), { credentials: "same-origin" })',
            '        .then(function (r) { return r.json(); })',
            '        .then(function (res) {',
            '          var html = (res && res.html) ? String(res.html) : "";',
            '          if (html.replace(/\s+/g, "").length > 0) {',
            '            appendCards(node, html);',
            '            page += 1;',
            '          }',
            '          var hasMore = !!(res && (res.has_more === 1 || res.has_more === true));',
            '          if (!hasMore && wrap.parentNode) {',
            '            wrap.parentNode.removeChild(wrap);',
            '          }',
            '        })',
            '        .catch(function () {})',
            '        .finally(function () {',
            '          loading = false;',
            '          if (btn && btn.isConnected) {',
            '            btn.disabled = false;',
            '            btn.style.opacity = "1";',
            '            btn.textContent = "Load More";',
            '          }',
            '        });',
            '    });',
            '  }',
            '',
            '  function loadSection(type, selector) {',
            '    var nodes = document.querySelectorAll(selector);',
            '    if (!nodes || !nodes.length) {',
            '      return;',
            '    }',
            '',
            '    for (var i = 0; i < nodes.length; i++) {',
            '      nodes[i].innerHTML = "<div class=\"aiwb-dyn-loading\">Loading...</div>";',
            '    }',
            '',
            '    fetch(buildUrl(type, 1), { credentials: "same-origin" })',
            '      .then(function (r) { return r.json(); })',
            '      .then(function (res) {',
            '        var html = (res && res.html) ? String(res.html) : "";',
            '        var hasMore = !!(res && (res.has_more === 1 || res.has_more === true));',
            '        for (var j = 0; j < nodes.length; j++) {',
            '          var node = nodes[j];',
            '          if (html.replace(/\s+/g, "").length === 0) {',
            '            hideClosest(node);',
            '            continue;',
            '          }',
            '          node.innerHTML = html;',
            '          hydrateDynamicRoot(node);',
            '          appendPostTokenToLinks(node);',
            '          if (hasMore) {',
            '            mountLoadMore(node, type, 1);',
            '          }',
            '        }',
            '      })',
            '      .catch(function () {',
            '        for (var k = 0; k < nodes.length; k++) {',
            '          hideClosest(nodes[k]);',
            '        }',
            '      });',
            '  }',
            '',
            '  function init() {',
            '    for (var i = 0; i < TYPES.length; i++) {',
            '      loadSection(TYPES[i].key, TYPES[i].selector);',
            '    }',
            '  }',
            '',
            '  if (document.readyState === "loading") {',
            '    document.addEventListener("DOMContentLoaded", init);',
            '  } else {',
            '    init();',
            '  }',
            '})();',
            '',
        ]);
    }

    private function appendPriorityHeaderOverrides(string $css): string
    {
        // Preserve AI-generated typography values, but make them win over theme CSS.
        // This avoids inheriting host-theme fonts/sizes on embedded pages like login.
        $css = preg_replace_callback(
            '/\b(font-family|font-size|font-weight|font-style|line-height|letter-spacing|text-transform)\s*:\s*([^;{}]+);/i',
            function (array $m): string {
                $prop = (string) ($m[1] ?? '');
                $value = trim((string) ($m[2] ?? ''));
                if ($prop === '' || $value === '') {
                    return (string) ($m[0] ?? '');
                }
                if (stripos($value, '!important') !== false) {
                    return $prop . ': ' . $value . ';';
                }

                return $prop . ': ' . $value . ' !important;';
            },
            $css
        ) ?? $css;
        
        // Footer block
        $css = preg_replace_callback(
            '/footer\s*\{([^}]*)\}/is',
            function (array $m): string {
                $body = $m[1];
    
                $body = preg_replace_callback(
                    '/([a-zA-Z\-]+)\s*:\s*([^;]+);/',
                    function (array $propMatch): string {
                        $prop  = trim($propMatch[1]);
                        $value = trim($propMatch[2]);
    
                        if (stripos($value, '!important') !== false) {
                            return "{$prop}: {$value};";
                        }
    
                        return "{$prop}: {$value} !important;";
                    },
                    $body
                );
    
                return "footer {{$body}}";
            },
            $css
        ) ?? $css;

        return $css;
    }

    private function removeGlobalImgDisplayRules(string $css): string
    {
        if (trim($css) === '') {
            return $css;
        }

        // Remove global img display declarations (img { display: ... }) while preserving
        // component-scoped selectors like .card img or #hero img.
        $css = preg_replace_callback('/(^|\})\s*img\s*\{([^}]*)\}/im', function (array $m): string {
            $prefix = (string) ($m[1] ?? '');
            $body = (string) ($m[2] ?? '');

            $cleanBody = preg_replace('/\bdisplay\s*:\s*[^;{}]+;?/i', '', $body) ?? $body;
            $cleanBody = trim((string) $cleanBody);

            if ($cleanBody === '') {
                return $prefix;
            }

            return $prefix . 'img{' . $cleanBody . '}';
        }, $css) ?? $css;

        return $css;
    }
}
