<?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.php.',
            'When output is split into header/footer files, include <script src="control/nav.js" defer></script> in header.php immediately after the CSS link, not in footer.php.',
            'When output is split into header/footer files, keep all mobile menubar code in header.php only; do not place mobile menubar markup or behavior scripts in index.php or footer.php.',
            '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.php
     *  - header.php
     *  - footer.php
     *  - css/script.css
     *  - control/nav.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');
        $cssDir = $root.DIRECTORY_SEPARATOR.'css';
        $jsDir = $root.DIRECTORY_SEPARATOR.'control';

        if (! is_dir($cssDir) && ! @mkdir($cssDir, 0775, true) && ! is_dir($cssDir)) {
            throw new \RuntimeException('Failed to create CSS directory.');
        }
        if (! is_dir($jsDir) && ! @mkdir($jsDir, 0775, true) && ! is_dir($jsDir)) {
            throw new \RuntimeException('Failed to create JS 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.php';
        $headerPath = $root.DIRECTORY_SEPARATOR.'header.php';
        $footerPath = $root.DIRECTORY_SEPARATOR.'footer.php';
        $cssPath = $cssDir.DIRECTORY_SEPARATOR.'script.css';
        $jsPath = $jsDir.DIRECTORY_SEPARATOR.'nav.js';

        $headerFile = '<link rel="stylesheet" href="css/script.css">' . "\n";
        $headerFile .= '<script src="control/nav.js" defer></script>' . "\n";
        if (trim($headerMarkup) !== '') {
            $headerFile .= trim($headerMarkup) . "\n";
        }

        $footerFile = '';
        if (trim($footerMarkup) !== '') {
            $footerFile .= trim($footerMarkup) . "\n";
        }

        file_put_contents($headerPath, $headerFile);
        file_put_contents($footerPath, $footerFile);
        file_put_contents($cssPath, $css);
        file_put_contents($jsPath, $js);
        file_put_contents($indexPath, $htmlFinal);

        return [
            'paths' => [
                'index' => $indexPath,
                'header' => $headerPath,
                'footer' => $footerPath,
                'css' => $cssPath,
                'js' => $jsPath,
            ],
            'urls' => [
                'index' => url('c/'.$communityId.'/web/index.php'),
                'header' => url('c/'.$communityId.'/web/header.php'),
                'footer' => url('c/'.$communityId.'/web/footer.php'),
                'css' => url('c/'.$communityId.'/web/css/script.css'),
                'js' => url('c/'.$communityId.'/web/control/nav.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
    {
        $headerInclude = $this->buildIncludeLine('header.php');
        $footerInclude = $this->buildIncludeLine('footer.php');

        // 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('/&lt;\?php\s+include\s+__DIR__\s*\.\s*["\']\/(header|footer)\.php["\']\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', $headerInclude, $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', $footerInclude, $html, 1) ?? $html;
        }

        if ($headerMarkup === '') {
            if (stripos($html, '<body') !== false) {
                $html = preg_replace('/<body\b[^>]*>/i', '$0' . "\n" . $headerInclude, $html, 1) ?? $html;
            } else {
                $html = $headerInclude . "\n" . $html;
            }
        }

        if ($footerMarkup === '') {
            if (stripos($html, '</body>') !== false) {
                $html = preg_replace('#</body>#i', $footerInclude . "\n</body>", $html, 1) ?? $html;
            } else {
                $html .= "\n" . $footerInclude . "\n";
            }
        }

        // If includes were HTML-encoded upstream, decode so PHP executes at runtime.
        $html = str_replace(['&lt;?php', '?&gt;'], ['<?php', '?>'], $html);

        return [$html, $headerMarkup, $footerMarkup];
    }

    private function buildIncludeLine(string $fileName): string
    {
        return "<?php include __DIR__ . '/" . trim($fileName) . "'; ?>";
    }

    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.php#aboutus"
        return preg_replace('/\bhref\s*=\s*(["\'])#([^"\'\s>]+)\1/i', 'href=$1index.php#$2$1', $markup) ?? $markup;
    }

    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;
    }
}
