<?php
/**
 * 芋头CMS - 页面装修渲染器
 */

namespace Core\Lib;

class BuilderRenderer
{
    private $db;
    private $site;
    private $templateRoot;
    private $pageLayout = [];

    public function __construct($db, array $site = [])
    {
        $this->db = $db;
        $this->site = $site;
        $this->templateRoot = ROOT_PATH . 'template/_builder/';
    }

    public function render(array $layout, array $context = [])
    {
        $this->pageLayout = $layout;
        $sections = isset($layout['sections']) && is_array($layout['sections']) ? $layout['sections'] : [];
        $pageTitle = trim((string) ($layout['page']['title'] ?? ($this->site['site_title'] ?? $this->site['site_name'] ?? '芋头CMS')));
        $seoTitle = trim((string) ($layout['page']['seo_title'] ?? ''));
        $seoKeywords = trim((string) ($layout['page']['seo_keywords'] ?? ''));
        $seoDescription = trim((string) ($layout['page']['seo_description'] ?? ''));
        $pageBackground = trim((string) ($layout['page']['background_color'] ?? ''));
        $pageBackgroundImage = trim((string) ($layout['page']['background_image'] ?? ''));
        $pageFontFamily = trim((string) ($layout['page']['font_family'] ?? ''));
        $pageWidth = max(320, intval($layout['page']['page_width'] ?? 1200));
        $pagePadding = max(0, intval($layout['page']['page_padding'] ?? 0));
        $mobilePageWidth = max(320, intval($layout['page']['mobile_page_width'] ?? 375));
        $mobilePagePadding = max(0, intval($layout['page']['mobile_page_padding'] ?? 12));
        $mobileEnabled = intval($layout['page']['mobile_enabled'] ?? 1) === 1;

        echo '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">';
        echo '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
        echo '<title>' . htmlspecialchars($seoTitle !== '' ? $seoTitle : $pageTitle, ENT_QUOTES, 'UTF-8') . '</title>';
        if ($seoKeywords !== '') {
            echo '<meta name="keywords" content="' . htmlspecialchars($seoKeywords, ENT_QUOTES, 'UTF-8') . '">';
        }
        if ($seoDescription !== '') {
            echo '<meta name="description" content="' . htmlspecialchars($seoDescription, ENT_QUOTES, 'UTF-8') . '">';
        }
        echo '<style>';
        echo 'body{margin:0;font-family:' . ($pageFontFamily !== '' ? htmlspecialchars($pageFontFamily, ENT_QUOTES, 'UTF-8') . ',' : '') . '-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#f8fafc;color:#0f172a;';
        if ($pageBackground !== '') {
            echo 'background:' . htmlspecialchars($pageBackground, ENT_QUOTES, 'UTF-8') . ';';
        }
        if ($pageBackgroundImage !== '') {
            echo 'background-image:url("' . htmlspecialchars($pageBackgroundImage, ENT_QUOTES, 'UTF-8') . '");background-size:cover;background-repeat:no-repeat;background-position:center top;';
        }
        echo '}';
        echo '.builder-page{min-height:100vh;}';
        echo '.builder-empty{max-width:900px;margin:80px auto;padding:40px;background:#fff;border:1px solid #e2e8f0;border-radius:20px;text-align:center;box-shadow:0 10px 30px rgba(15,23,42,.06);}';
        echo '.builder-empty h2{margin:0 0 12px;font-size:28px;color:#111827;}';
        echo '.builder-empty p{margin:0;color:#64748b;line-height:1.8;}';
        echo '.builder-section{padding:56px 20px;}';
        echo '.builder-wrap{max-width:' . $pageWidth . 'px;margin:0 auto;padding-left:' . $pagePadding . 'px;padding-right:' . $pagePadding . 'px;}';
        echo '.builder-free-layout{position:relative;max-width:' . $pageWidth . 'px;margin:32px auto 48px;padding-left:' . $pagePadding . 'px;padding-right:' . $pagePadding . 'px;box-sizing:border-box;min-height:240px;}';
        echo '.builder-free-layout-item{position:absolute;box-sizing:border-box;}';
        echo '.builder-free-layout-item>.builder-section{padding:0;}';
        echo '.builder-free-layout-item>.builder-section>.builder-wrap{max-width:none;padding-left:0;padding-right:0;}';
        echo '.builder-grid{display:grid;gap:24px;}';
        echo '.builder-card{background:#fff;border:1px solid #e5e7eb;border-radius:18px;padding:24px;box-shadow:0 8px 20px rgba(15,23,42,.04);color:inherit;}';
        echo '.builder-title{margin:0 0 12px;font-size:30px;line-height:1.2;color:inherit;}';
        echo '.builder-subtitle{margin:0;color:inherit;opacity:.82;line-height:1.8;}';
        echo '.builder-btn{display:inline-flex;align-items:center;justify-content:center;padding:12px 22px;border-radius:999px;background:#111827;color:#fff;text-decoration:none;font-weight:700;}';
        echo '.builder-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:18px;}';
        echo '.builder-item{background:#fff;border:1px solid #e5e7eb;border-radius:16px;padding:18px;color:inherit;}';
        echo '.builder-item img{width:100%;height:220px;object-fit:cover;border-radius:12px;background:#f1f5f9;}';
        echo '.builder-contact{display:grid;gap:10px;font-size:15px;color:#475569;}';
        echo '.builder-canvas{position:relative;width:min(var(--builder-canvas-width,1200px),calc(100vw - 40px));min-height:var(--builder-canvas-height,0px);margin:32px auto 48px;}';
        echo '.builder-canvas-block{position:absolute;box-sizing:border-box;min-width:48px;min-height:32px;border-radius:16px;overflow:hidden;}';
        echo '.builder-canvas-block-inner{width:100%;height:100%;box-sizing:border-box;overflow:hidden;border-radius:inherit;background:#fff;}';
        echo '.builder-canvas-block img{display:block;max-width:100%;height:100%;object-fit:cover;}';
        echo '.builder-extra-zone{max-width:' . $pageWidth . 'px;margin:0 auto;padding-left:' . $pagePadding . 'px;padding-right:' . $pagePadding . 'px;box-sizing:border-box;}';
        echo '.builder-extra-item{display:block;}';
        echo '.builder-block-locked{position:relative;}';
        if ($mobileEnabled) {
            echo '@media(max-width:768px){.builder-section{padding:36px 16px;}.builder-title{font-size:24px;}.builder-wrap{max-width:' . $mobilePageWidth . 'px;padding-left:' . $mobilePagePadding . 'px;padding-right:' . $mobilePagePadding . 'px;}.builder-extra-zone{max-width:' . $mobilePageWidth . 'px;padding-left:' . $mobilePagePadding . 'px;padding-right:' . $mobilePagePadding . 'px;}.builder-canvas{width:min(var(--builder-canvas-width-mobile,' . $mobilePageWidth . 'px),calc(100vw - 24px));margin:24px auto 36px;}}';
        }
        echo '</style></head><body><div class="builder-page">';
        echo $this->renderPageWidgets();

        if (empty($sections)) {
            echo '<div class="builder-empty"><h2>页面暂未装修</h2><p>请先在后台“页面装修”中添加组件并保存发布。</p></div>';
        } else {
            $flowSections = [];
            $freeSections = [];
            $freeHeight = 0;
            foreach ($sections as $index => $section) {
                if (!empty($section['hidden'])) {
                    continue;
                }
                $layoutMeta = isset($section['layout']) && is_array($section['layout']) ? $section['layout'] : [];
                $positionMode = trim((string) ($layoutMeta['position_mode'] ?? 'flow'));
                if ($positionMode === 'free') {
                    $normalizedLayout = BuilderComponents::normalizeSectionLayout($layoutMeta, (string) ($section['type'] ?? ''), intval($index));
                    $section['layout'] = $normalizedLayout;
                    $freeSections[] = $section;
                    $freeHeight = max($freeHeight, intval($normalizedLayout['y'] ?? 0) + intval($normalizedLayout['h'] ?? 0) + 40);
                } else {
                    $flowSections[] = $section;
                }
            }
            foreach ($flowSections as $section) {
                $this->renderSection($section, $context);
            }
            if ($freeSections) {
                echo '<div class="builder-free-layout" style="min-height:' . max(260, $freeHeight) . 'px;">';
                foreach ($freeSections as $section) {
                    $layoutMeta = $section['layout'] ?? [];
                    $left = max(0, intval($layoutMeta['x'] ?? 0));
                    $top = max(0, intval($layoutMeta['y'] ?? 0));
                    $width = max(220, intval($layoutMeta['w'] ?? 320));
                    $height = max(120, intval($layoutMeta['h'] ?? 180));
                    $zIndex = max(1, intval($layoutMeta['z'] ?? 1));
                    echo '<div class="builder-free-layout-item" style="left:' . $left . 'px;top:' . $top . 'px;width:' . $width . 'px;height:' . $height . 'px;z-index:' . $zIndex . ';">';
                    $this->renderSection($section, $context);
                    echo '</div>';
                }
                echo '</div>';
            }
        }
        echo $this->renderExtraSections();
        echo $this->renderCanvasBlocks();
        echo $this->renderFrontendOverridesScript();

        echo '</div></body></html>';
    }

    private function renderSection(array $section, array $context = [])
    {
        $type = trim((string) ($section['type'] ?? ''));
        $props = isset($section['props']) && is_array($section['props']) ? $section['props'] : [];
        $styles = isset($section['styles']) && is_array($section['styles']) ? $section['styles'] : [];
        $componentFile = $this->templateRoot . $type . '.php';
        $sectionStyle = $this->buildSectionStyle($styles);

        echo '<section class="builder-section builder-section-' . htmlspecialchars($type, ENT_QUOTES, 'UTF-8') . '" style="' . htmlspecialchars($sectionStyle['section'], ENT_QUOTES, 'UTF-8') . '"><div class="builder-wrap">';
        if (is_file($componentFile)) {
            $db = $this->db;
            $site = $this->site;
            $builderStyles = $sectionStyle;
            include $componentFile;
        } else {
            echo '<div class="builder-card" style="' . htmlspecialchars($sectionStyle['card'], ENT_QUOTES, 'UTF-8') . '">';
            echo '<div style="font-size:12px;color:#94a3b8;margin-bottom:8px;">未找到组件模板</div>';
            echo '<h3 style="margin:0;font-size:20px;color:#111827;">' . htmlspecialchars($type, ENT_QUOTES, 'UTF-8') . '</h3>';
            echo '</div>';
        }
        echo '</div></section>';
    }

    private function buildSectionStyle(array $styles)
    {
        $sectionCss = [];
        $cardCss = [];
        $buttonCss = [];

        $paddingTop = intval($styles['section_padding_top'] ?? 56);
        $paddingBottom = intval($styles['section_padding_bottom'] ?? 56);
        $radius = intval($styles['section_radius'] ?? 18);

        $sectionCss[] = 'padding-top:' . max(0, $paddingTop) . 'px';
        $sectionCss[] = 'padding-bottom:' . max(0, $paddingBottom) . 'px';

        if (!empty($styles['section_bg_color'])) {
            $sectionCss[] = 'background:' . trim((string) $styles['section_bg_color']);
        }
        if (!empty($styles['section_text_color'])) {
            $sectionCss[] = 'color:' . trim((string) $styles['section_text_color']);
        }
        if (!empty($styles['card_bg_color'])) {
            $cardCss[] = 'background:' . trim((string) $styles['card_bg_color']);
        }
        if ($radius > 0) {
            $cardCss[] = 'border-radius:' . $radius . 'px';
        }
        if (!empty($styles['button_bg_color'])) {
            $buttonCss[] = 'background:' . trim((string) $styles['button_bg_color']);
        }
        if (!empty($styles['button_text_color'])) {
            $buttonCss[] = 'color:' . trim((string) $styles['button_text_color']);
        }
        if ($radius > 0) {
            $buttonCss[] = 'border-radius:' . $radius . 'px';
        }

        return [
            'section' => implode(';', $sectionCss) . ';',
            'card' => implode(';', $cardCss) . ';',
            'button' => implode(';', $buttonCss) . ';',
        ];
    }

    private function renderCanvasBlocks()
    {
        $page = isset($this->pageLayout['page']) && is_array($this->pageLayout['page']) ? $this->pageLayout['page'] : [];
        $blocks = isset($page['frontend_canvas_blocks']) && is_array($page['frontend_canvas_blocks']) ? $page['frontend_canvas_blocks'] : [];
        if (!$blocks) {
            return '';
        }

        $pageWidth = max(320, intval($page['page_width'] ?? 1200));
        $mobilePageWidth = max(320, intval($page['mobile_page_width'] ?? 375));
        $height = 0;
        $html = [];
        foreach ($blocks as $block) {
            if (!is_array($block) || !empty($block['hidden'])) {
                continue;
            }
            $x = max(0, intval($block['x'] ?? 0));
            $y = max(0, intval($block['y'] ?? 0));
            $w = max(48, intval($block['w'] ?? 320));
            $h = max(32, intval($block['h'] ?? 160));
            $z = max(1, intval($block['z'] ?? 1));
            $type = trim((string) ($block['type'] ?? 'text'));
            $style = isset($block['style']) && is_array($block['style']) ? $block['style'] : [];
            $content = isset($block['content']) && is_array($block['content']) ? $block['content'] : [];
            $height = max($height, $y + $h + 80);

            $radius = max(0, intval($style['radius'] ?? 16));
            $outerStyle = 'left:' . $x . 'px;top:' . $y . 'px;width:' . $w . 'px;height:' . $h . 'px;z-index:' . $z . ';border-radius:' . $radius . 'px;';
            $innerStyle = '';
            if (!empty($style['background'])) {
                $innerStyle .= 'background:' . htmlspecialchars((string) $style['background'], ENT_QUOTES, 'UTF-8') . ';';
            }
            if (!empty($style['color'])) {
                $innerStyle .= 'color:' . htmlspecialchars((string) $style['color'], ENT_QUOTES, 'UTF-8') . ';';
            }
            if (array_key_exists('padding', $style)) {
                $innerStyle .= 'padding:' . max(0, intval($style['padding'])) . 'px;';
            }
            $innerStyle .= 'border-radius:' . $radius . 'px;';

            $body = '';
            if ($type === 'image') {
                $fit = trim((string) ($style['fit'] ?? 'cover'));
                $body = '<img src="' . htmlspecialchars((string) ($content['src'] ?? ''), ENT_QUOTES, 'UTF-8') . '" alt="' . htmlspecialchars((string) ($content['alt'] ?? ''), ENT_QUOTES, 'UTF-8') . '" style="width:100%;height:100%;object-fit:' . htmlspecialchars($fit !== '' ? $fit : 'cover', ENT_QUOTES, 'UTF-8') . ';">';
            } elseif ($type === 'button') {
                $bg = htmlspecialchars((string) ($style['background'] ?? '#111827'), ENT_QUOTES, 'UTF-8');
                $color = htmlspecialchars((string) ($style['color'] ?? '#fff'), ENT_QUOTES, 'UTF-8');
                $body = '<a href="' . htmlspecialchars((string) ($content['href'] ?? 'javascript:void(0)'), ENT_QUOTES, 'UTF-8') . '" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;box-sizing:border-box;border-radius:' . $radius . 'px;background:' . $bg . ';color:' . $color . ';font-weight:800;text-decoration:none;">' . htmlspecialchars((string) ($content['text'] ?? '按钮'), ENT_QUOTES, 'UTF-8') . '</a>';
            } elseif ($type === 'html') {
                $body = (string) ($content['html'] ?? '');
            } else {
                $body = '<h3 style="margin:0;font-size:24px;line-height:1.35;color:inherit;">' . htmlspecialchars((string) ($content['title'] ?? '标题文本'), ENT_QUOTES, 'UTF-8') . '</h3><p style="margin:12px 0 0;color:inherit;opacity:.76;line-height:1.8;">' . htmlspecialchars((string) ($content['text'] ?? ''), ENT_QUOTES, 'UTF-8') . '</p>';
            }

            $html[] = '<div class="builder-canvas-block" style="' . htmlspecialchars($outerStyle, ENT_QUOTES, 'UTF-8') . '"><div class="builder-canvas-block-inner" style="' . $innerStyle . '">' . $body . '</div></div>';
        }
        if (!$html) {
            return '';
        }
        return '<div class="builder-canvas" style="--builder-canvas-width:' . $pageWidth . 'px;--builder-canvas-width-mobile:' . $mobilePageWidth . 'px;--builder-canvas-height:' . max(120, $height) . 'px;">' . implode('', $html) . '</div>';
    }

    private function renderExtraSections()
    {
        $page = isset($this->pageLayout['page']) && is_array($this->pageLayout['page']) ? $this->pageLayout['page'] : [];
        $extras = isset($page['frontend_extra_sections']) && is_array($page['frontend_extra_sections']) ? $page['frontend_extra_sections'] : [];
        if (!$extras) {
            return '';
        }

        $html = [];
        foreach ($extras as $item) {
            if (!is_array($item) || empty($item['html'])) {
                continue;
            }
            $extraId = htmlspecialchars((string) ($item['id'] ?? ''), ENT_QUOTES, 'UTF-8');
            $extraType = htmlspecialchars((string) ($item['type'] ?? 'html'), ENT_QUOTES, 'UTF-8');
            $config = isset($item['config']) && is_array($item['config']) ? $item['config'] : [];
            $configJson = json_encode($config, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            if (!is_string($configJson)) {
                $configJson = '{}';
            }
            $html[] = '<section class="builder-extra-item" data-builder-extra-id="' . $extraId . '" data-builder-extra-type="' . $extraType . '" data-builder-extra-config="' . htmlspecialchars($configJson, ENT_QUOTES, 'UTF-8') . '">' . (string) $item['html'] . '</section>';
        }

        if (!$html) {
            return '';
        }

        return '<div class="builder-extra-zone">' . implode('', $html) . '</div>';
    }

    private function renderFrontendOverridesScript()
    {
        $page = isset($this->pageLayout['page']) && is_array($this->pageLayout['page']) ? $this->pageLayout['page'] : [];
        $payload = [
            'desktop_overrides' => isset($page['frontend_overrides']) && is_array($page['frontend_overrides']) ? $page['frontend_overrides'] : [],
            'mobile_overrides' => isset($page['frontend_mobile_overrides']) && is_array($page['frontend_mobile_overrides']) ? $page['frontend_mobile_overrides'] : [],
            'desktop_hidden_paths' => isset($page['frontend_hidden_paths']) && is_array($page['frontend_hidden_paths']) ? $page['frontend_hidden_paths'] : [],
            'desktop_deleted_paths' => isset($page['frontend_deleted_paths']) && is_array($page['frontend_deleted_paths']) ? $page['frontend_deleted_paths'] : [],
            'mobile_hidden_paths' => isset($page['frontend_mobile_hidden_paths']) && is_array($page['frontend_mobile_hidden_paths']) ? $page['frontend_mobile_hidden_paths'] : [],
            'mobile_deleted_paths' => isset($page['frontend_mobile_deleted_paths']) && is_array($page['frontend_mobile_deleted_paths']) ? $page['frontend_mobile_deleted_paths'] : [],
            'locked_paths' => isset($page['frontend_locked_paths']) && is_array($page['frontend_locked_paths']) ? $page['frontend_locked_paths'] : [],
            'block_orders' => isset($page['frontend_block_orders']) && is_array($page['frontend_block_orders']) ? $page['frontend_block_orders'] : [],
            'extra_sections' => isset($page['frontend_extra_sections']) && is_array($page['frontend_extra_sections']) ? $page['frontend_extra_sections'] : [],
        ];
        $hasState = !empty($payload['desktop_overrides'])
            || !empty($payload['mobile_overrides'])
            || !empty($payload['desktop_hidden_paths'])
            || !empty($payload['desktop_deleted_paths'])
            || !empty($payload['mobile_hidden_paths'])
            || !empty($payload['mobile_deleted_paths'])
            || !empty($payload['locked_paths'])
            || !empty($payload['block_orders'])
            || !empty($payload['extra_sections']);
        if (!$hasState) {
            return '';
        }

        $json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($json) || $json === '') {
            return '';
        }

        $script = <<<JS
<script>
(function(){
    var state = {$json};
    function q(path){
        var node = null;
        try {
            node = document.querySelector(path);
        } catch (error) {
            node = null;
        }
        return node;
    }
    function findByAttr(name, value){
        var matched = null;
        var expected = String(value || "");
        Array.prototype.some.call(document.querySelectorAll("[" + name + "]"), function(node){
            if ((node.getAttribute(name) || "") === expected) {
                matched = node;
                return true;
            }
            return false;
        });
        return matched;
    }
    function normalizeRef(value){
        if (!value) {
            return {path:"", key:""};
        }
        if (typeof value === "string") {
            return {path:value, key:""};
        }
        return {
            path: typeof value.path === "string" ? value.path : "",
            key: typeof value.key === "string" ? value.key : ""
        };
    }
    function queryByRef(ref){
        var normalized = normalizeRef(ref);
        var node = null;
        if (normalized.key) {
            node = findByAttr("data-builder-key", normalized.key);
            if (node) {
                return node;
            }
        }
        return normalized.path ? q(normalized.path) : null;
    }
    function queryOverrideRef(bucketKey){
        if (typeof bucketKey !== "string") {
            return {path:"", key:""};
        }
        if (bucketKey.indexOf("key:") === 0) {
            return {key: bucketKey.slice(4), path:""};
        }
        if (bucketKey.indexOf("path:") === 0) {
            return {path: bucketKey.slice(5), key:""};
        }
        return {path: bucketKey, key:""};
    }
    function isMobile(){
        return !!(window.matchMedia && window.matchMedia("(max-width:768px)").matches);
    }
    function applyOrders(list){
        (list || []).forEach(function(entry){
            if (!entry || !entry.parent || !Array.isArray(entry.paths)) {
                return;
            }
            var parent = queryByRef(entry.parent);
            if (!parent) {
                return;
            }
            entry.paths.forEach(function(ref){
                var child = queryByRef(ref);
                if (child && child.parentNode === parent) {
                    parent.appendChild(child);
                }
            });
        });
    }
    function applyExtras(list){
        var zone = document.querySelector(".builder-extra-zone");
        (list || []).forEach(function(item){
            if (!item || !item.id) {
                return;
            }
            var node = findByAttr("data-builder-extra-id", item.id);
            if (!node) {
                return;
            }
            var anchor = queryByRef(item.anchor_ref || item.anchor_path || null);
            if (anchor && anchor.parentNode) {
                if (item.insert_mode === "before") {
                    anchor.parentNode.insertBefore(node, anchor);
                } else {
                    anchor.parentNode.insertBefore(node, anchor.nextSibling);
                }
            } else if (zone && node.parentNode !== zone) {
                zone.appendChild(node);
            }
        });
    }
    function applyVisibility(paths, deleted){
        (paths || []).forEach(function(ref){
            var node = queryByRef(ref);
            if (!node) {
                return;
            }
            node.setAttribute(deleted ? "data-builder-deleted" : "data-builder-hidden", "1");
            node.style.setProperty("display", "none", "important");
        });
    }
    function applyLocks(paths){
        (paths || []).forEach(function(ref){
            var node = queryByRef(ref);
            if (node) {
                node.classList.add("builder-block-locked");
            }
        });
    }
    function applyOverrides(map){
        Object.keys(map || {}).forEach(function(bucketKey){
            var node = queryByRef(queryOverrideRef(bucketKey));
            if (!node) {
                return;
            }
            var data = map[bucketKey] || {};
            if (Object.prototype.hasOwnProperty.call(data, "text")) {
                node.textContent = data.text == null ? "" : String(data.text);
            }
            if (Object.prototype.hasOwnProperty.call(data, "html")) {
                node.innerHTML = data.html == null ? "" : String(data.html);
            }
            if (Object.prototype.hasOwnProperty.call(data, "src") && node.tagName && node.tagName.toLowerCase() === "img") {
                node.setAttribute("src", data.src || "");
            }
            if (Object.prototype.hasOwnProperty.call(data, "href") && node.tagName && node.tagName.toLowerCase() === "a") {
                node.setAttribute("href", data.href || "javascript:void(0)");
            }
            if (Object.prototype.hasOwnProperty.call(data, "style")) {
                node.setAttribute("style", data.style || "");
            }
        });
    }
    var mobile = isMobile();
    applyOrders(state.block_orders || []);
    applyExtras(state.extra_sections || []);
    applyVisibility(mobile ? state.mobile_hidden_paths : state.desktop_hidden_paths, false);
    applyVisibility(mobile ? state.mobile_deleted_paths : state.desktop_deleted_paths, true);
    applyLocks(state.locked_paths || []);
    applyOverrides(mobile ? state.mobile_overrides : state.desktop_overrides);
})();
</script>
JS;
        return $script;
    }

    private function renderPageWidgets()
    {
        $page = isset($this->pageLayout['page']) && is_array($this->pageLayout['page']) ? $this->pageLayout['page'] : [];
        $widgets = $this->normalizeBooleanMap($page['site_widgets'] ?? null, [
            'floating_service' => 0,
            'site_sidebar' => 0,
            'top_ad' => 0,
            'bottom_float' => 0,
            'member_bar' => 0,
            'home_popup' => 0,
            'elevator_nav' => 0,
            'welcome_screen' => 0,
            'counter' => 0,
            'background_music' => 0,
        ]);
        $configs = $this->normalizeWidgetConfigs($page['site_widget_configs'] ?? null, [
            'top_ad' => ['text' => '网站顶部广告：立即领取专属咨询通道', 'link_url' => ''],
            'member_bar' => ['login_text' => '会员登录', 'register_text' => '立即注册'],
            'floating_service' => ['guestbook_url' => '', 'phone' => '', 'qq' => '', 'wechat' => '', 'email' => ''],
            'site_sidebar' => ['product_url' => '', 'contact_label' => '联系'],
            'elevator_nav' => ['product_url' => '', 'contact_label' => '联系'],
            'bottom_float' => ['text' => '立即获取专属方案', 'button_text' => '立即咨询', 'button_url' => ''],
            'counter' => ['label' => '本页访问'],
            'background_music' => ['audio_url' => '', 'button_text' => '播放音乐', 'autoplay' => 0],
            'home_popup' => ['title' => '首页弹窗', 'content' => '这里可以展示活动信息、优惠券或者首页欢迎文案。', 'button_text' => '立即查看', 'button_url' => ''],
            'welcome_screen' => ['title' => 'WELCOME', 'content' => '欢迎页已开启，适合活动场景或品牌开场。', 'button_text' => '进入网站'],
        ]);

        $guestbookUrl = $this->guessGuestbookUrl();
        $productUrl = $this->guessProductUrl();
        $siteName = trim((string) ($this->site['site_name'] ?? $this->site['site_title'] ?? '企业官网'));
        $phone = $this->widgetText($configs, 'floating_service', 'phone', (string) ($this->site['site_phone'] ?? $this->site['site_tel'] ?? ''));
        $qq = $this->widgetText($configs, 'floating_service', 'qq', (string) ($this->site['site_qq'] ?? ''));
        $wechat = $this->widgetText($configs, 'floating_service', 'wechat', (string) ($this->site['site_wechat'] ?? ''));
        $email = $this->widgetText($configs, 'floating_service', 'email', (string) ($this->site['site_email'] ?? ''));
        $serviceUrl = $this->widgetText($configs, 'floating_service', 'guestbook_url', $guestbookUrl);
        $sidebarProductUrl = $this->widgetText($configs, 'site_sidebar', 'product_url', $this->widgetText($configs, 'elevator_nav', 'product_url', $productUrl));
        $sidebarContactLabel = $this->widgetText($configs, 'site_sidebar', 'contact_label', $this->widgetText($configs, 'elevator_nav', 'contact_label', '联系'));
        $parts = [];

        if ($widgets['top_ad']) {
            $parts[] = '<a href="' . htmlspecialchars($this->widgetText($configs, 'top_ad', 'link_url', $serviceUrl), ENT_QUOTES, 'UTF-8') . '" style="position:fixed;left:50%;top:14px;transform:translateX(-50%);z-index:2147483300;padding:10px 18px;border-radius:999px;background:#111827;color:#fff;box-shadow:0 16px 36px rgba(15,23,42,.2);text-decoration:none;">' . htmlspecialchars($this->widgetText($configs, 'top_ad', 'text', '网站顶部广告：立即领取专属咨询通道'), ENT_QUOTES, 'UTF-8') . '</a>';
        }
        if ($widgets['member_bar']) {
            $parts[] = '<div style="position:fixed;right:24px;top:56px;z-index:2147483298;display:flex;gap:8px;"><a href="/member/login.php" style="padding:10px 14px;border-radius:999px;background:#111827;color:#fff;text-decoration:none;">' . htmlspecialchars($this->widgetText($configs, 'member_bar', 'login_text', '会员登录'), ENT_QUOTES, 'UTF-8') . '</a><a href="/member/register.php" style="padding:10px 14px;border-radius:999px;background:#fff;border:1px solid #dbe3f4;color:#111827;text-decoration:none;">' . htmlspecialchars($this->widgetText($configs, 'member_bar', 'register_text', '立即注册'), ENT_QUOTES, 'UTF-8') . '</a></div>';
        }
        if ($widgets['floating_service']) {
            $serviceItems = [];
            $serviceItems[] = '<a href="' . htmlspecialchars($serviceUrl, ENT_QUOTES, 'UTF-8') . '" style="border:0;border-radius:14px;padding:12px 14px;background:#111827;color:#fff;box-shadow:0 14px 30px rgba(15,23,42,.18);text-decoration:none;text-align:center;">在线咨询</a>';
            if ($phone !== '') {
                $serviceItems[] = '<a href="tel:' . htmlspecialchars($phone, ENT_QUOTES, 'UTF-8') . '" style="border:1px solid #dbe3f4;border-radius:14px;padding:12px 14px;background:#fff;color:#111827;text-decoration:none;text-align:center;">电话客服</a>';
            }
            if ($qq !== '') {
                $serviceItems[] = '<a href="https://wpa.qq.com/msgrd?v=3&uin=' . htmlspecialchars($qq, ENT_QUOTES, 'UTF-8') . '&site=qq&menu=yes" target="_blank" style="border:1px solid #dbe3f4;border-radius:14px;padding:12px 14px;background:#fff;color:#111827;text-decoration:none;text-align:center;">QQ咨询</a>';
            }
            if ($wechat !== '') {
                $serviceItems[] = '<div style="border:1px solid #dbe3f4;border-radius:14px;padding:12px 14px;background:#fff;color:#111827;text-align:center;">微信：' . htmlspecialchars($wechat, ENT_QUOTES, 'UTF-8') . '</div>';
            }
            if ($email !== '') {
                $serviceItems[] = '<a href="mailto:' . htmlspecialchars($email, ENT_QUOTES, 'UTF-8') . '" style="border:1px solid #dbe3f4;border-radius:14px;padding:12px 14px;background:#fff;color:#111827;text-decoration:none;text-align:center;">邮件联系</a>';
            }
            $parts[] = '<div style="position:fixed;right:18px;top:50%;transform:translateY(-50%);z-index:2147483296;display:grid;gap:10px;">' . implode('', $serviceItems) . '</div>';
        }
        if ($widgets['site_sidebar'] || $widgets['elevator_nav']) {
            $parts[] = '<div style="position:fixed;left:18px;top:50%;transform:translateY(-50%);z-index:2147483294;display:grid;gap:8px;"><a href="#top" style="border:0;border-radius:12px;padding:10px 12px;background:#111827;color:#fff;text-decoration:none;text-align:center;">回顶部</a><a href="' . htmlspecialchars($sidebarProductUrl, ENT_QUOTES, 'UTF-8') . '" style="border:1px solid #dbe3f4;border-radius:12px;padding:10px 12px;background:#fff;color:#111827;text-decoration:none;text-align:center;">产品</a><a href="#footer" style="border:1px solid #dbe3f4;border-radius:12px;padding:10px 12px;background:#fff;color:#111827;text-decoration:none;text-align:center;">' . htmlspecialchars($sidebarContactLabel, ENT_QUOTES, 'UTF-8') . '</a></div>';
        }
        if ($widgets['bottom_float']) {
            $buttonUrl = $this->widgetText($configs, 'bottom_float', 'button_url', $serviceUrl);
            $parts[] = '<div style="position:fixed;left:50%;bottom:18px;transform:translateX(-50%);z-index:2147483292;display:flex;align-items:center;gap:14px;min-width:min(640px,calc(100vw - 40px));justify-content:space-between;padding:14px 18px;border-radius:18px;background:#111827;color:#fff;box-shadow:0 20px 40px rgba(15,23,42,.24);"><span>' . htmlspecialchars($siteName, ENT_QUOTES, 'UTF-8') . '：' . htmlspecialchars($this->widgetText($configs, 'bottom_float', 'text', '立即获取专属方案'), ENT_QUOTES, 'UTF-8') . '</span><a href="' . htmlspecialchars($buttonUrl, ENT_QUOTES, 'UTF-8') . '" style="border:0;border-radius:999px;padding:10px 16px;background:#fff;color:#111827;font-weight:700;text-decoration:none;">' . htmlspecialchars($this->widgetText($configs, 'bottom_float', 'button_text', '立即咨询'), ENT_QUOTES, 'UTF-8') . '</a></div>';
        }
        if ($widgets['counter']) {
            $parts[] = '<div style="position:fixed;left:18px;bottom:18px;z-index:2147483290;padding:12px 14px;border-radius:16px;background:#fff;border:1px solid #e2e8f0;box-shadow:0 12px 30px rgba(15,23,42,.1);"><strong style="font-size:22px;color:#111827;">--</strong><span style="margin-left:8px;color:#64748b;">' . htmlspecialchars($this->widgetText($configs, 'counter', 'label', '本页访问'), ENT_QUOTES, 'UTF-8') . '</span></div>';
        }
        if ($widgets['background_music']) {
            $audioUrl = $this->widgetText($configs, 'background_music', 'audio_url', '');
            if ($audioUrl !== '') {
                $parts[] = '<audio controls' . (!empty($configs['background_music']['autoplay']) ? ' autoplay' : '') . ' preload="none" src="' . htmlspecialchars($audioUrl, ENT_QUOTES, 'UTF-8') . '" style="position:fixed;right:20px;bottom:92px;z-index:2147483288;max-width:260px;"></audio>';
            } else {
                $parts[] = '<div style="position:fixed;right:20px;bottom:92px;z-index:2147483288;padding:0 14px;height:48px;display:flex;align-items:center;border-radius:999px;background:#111827;color:#fff;box-shadow:0 16px 32px rgba(15,23,42,.2);">' . htmlspecialchars($this->widgetText($configs, 'background_music', 'button_text', '播放音乐'), ENT_QUOTES, 'UTF-8') . '</div>';
            }
        }
        if ($widgets['home_popup']) {
            $parts[] = '<div style="position:fixed;inset:0;z-index:2147483286;background:rgba(15,23,42,.42);display:flex;align-items:center;justify-content:center;padding:24px;"><div style="position:relative;width:min(460px,calc(100vw - 32px));padding:28px;border-radius:22px;background:#fff;box-shadow:0 28px 80px rgba(15,23,42,.28);text-align:center;"><div style="font-size:30px;font-weight:800;color:#111827;">' . htmlspecialchars($this->widgetText($configs, 'home_popup', 'title', '首页弹窗'), ENT_QUOTES, 'UTF-8') . '</div><div style="margin-top:12px;color:#64748b;line-height:1.9;">' . htmlspecialchars($this->widgetText($configs, 'home_popup', 'content', '这里可以展示活动信息、优惠券或者首页欢迎文案。'), ENT_QUOTES, 'UTF-8') . '</div><a href="' . htmlspecialchars($this->widgetText($configs, 'home_popup', 'button_url', $serviceUrl), ENT_QUOTES, 'UTF-8') . '" style="display:inline-flex;margin-top:18px;border:0;border-radius:999px;padding:10px 18px;background:#111827;color:#fff;text-decoration:none;">' . htmlspecialchars($this->widgetText($configs, 'home_popup', 'button_text', '立即查看'), ENT_QUOTES, 'UTF-8') . '</a></div></div>';
        }
        if ($widgets['welcome_screen']) {
            $parts[] = '<div style="position:fixed;inset:0;z-index:2147483284;background:linear-gradient(135deg,#0f172a,#1d4ed8);display:flex;align-items:center;justify-content:center;padding:24px;color:#fff;"><div style="text-align:center;"><div style="font-size:42px;font-weight:900;letter-spacing:4px;">' . htmlspecialchars($this->widgetText($configs, 'welcome_screen', 'title', 'WELCOME'), ENT_QUOTES, 'UTF-8') . '</div><div style="margin-top:14px;color:rgba(255,255,255,.76);line-height:1.9;">' . htmlspecialchars($this->widgetText($configs, 'welcome_screen', 'content', '欢迎页已开启，适合活动场景或品牌开场。'), ENT_QUOTES, 'UTF-8') . '</div><div style="margin-top:22px;display:inline-flex;border:0;border-radius:999px;padding:10px 20px;background:#fff;color:#111827;font-weight:700;">' . htmlspecialchars($this->widgetText($configs, 'welcome_screen', 'button_text', '进入网站'), ENT_QUOTES, 'UTF-8') . '</div></div></div>';
        }

        return implode('', $parts);
    }

    private function widgetText(array $configs, $widgetKey, $fieldKey, $fallback = '')
    {
        $value = $configs[$widgetKey][$fieldKey] ?? '';
        $value = trim((string) $value);
        return $value !== '' ? $value : trim((string) $fallback);
    }

    private function normalizeBooleanMap($value, array $defaults)
    {
        if (!is_array($value)) {
            return $defaults;
        }
        foreach ($defaults as $key => $defaultValue) {
            $defaults[$key] = !empty($value[$key]) ? 1 : 0;
        }
        return $defaults;
    }

    private function normalizeWidgetConfigs($value, array $defaults)
    {
        if (!is_array($value)) {
            return $defaults;
        }
        foreach ($defaults as $widgetKey => $fields) {
            if (!isset($value[$widgetKey]) || !is_array($value[$widgetKey])) {
                continue;
            }
            foreach ($fields as $fieldKey => $fieldDefault) {
                if (!array_key_exists($fieldKey, $value[$widgetKey])) {
                    continue;
                }
                if (is_int($fieldDefault)) {
                    $defaults[$widgetKey][$fieldKey] = !empty($value[$widgetKey][$fieldKey]) ? 1 : 0;
                } else {
                    $defaults[$widgetKey][$fieldKey] = trim((string) $value[$widgetKey][$fieldKey]);
                }
            }
        }
        return $defaults;
    }

    private function guessGuestbookUrl()
    {
        return '/public/index.php?m=guestbook';
    }

    private function guessProductUrl()
    {
        return '/public/index.php?m=product';
    }
}
