import { extension_settings, getContext } from "../../../../extensions.js";
import { createModuleEvents, event_types } from "../core/event-manager.js";
import { EXT_ID } from "../core/constants.js";
import { xbLog, CacheRegistry } from "../core/debug-core.js";
import { replaceXbGetVarInString } from "./variables/var-commands.js";
import { executeSlashCommand } from "../core/slash-command.js";
import { default_user_avatar, default_avatar } from "../../../../../script.js";
import { getIframeBaseScript, getWrapperScript } from "../core/wrapper-inline.js";
import { postToIframe, getIframeTargetOrigin, getTrustedOrigin } from "../core/iframe-messaging.js";
const MODULE_ID = 'iframeRenderer';
const events = createModuleEvents(MODULE_ID);

let isGenerating = false;
const winMap = new Map();
let lastHeights = new WeakMap();
const blobUrls = new WeakMap();
const hashToBlobUrl = new Map();
const hashToBlobBytes = new Map();
const blobLRU = [];
const BLOB_CACHE_LIMIT = 32;
let lastApplyTs = 0;
let pendingHeight = null;
let pendingRec = null;

CacheRegistry.register(MODULE_ID, {
    name: 'Blob URL 缓存',
    getSize: () => hashToBlobUrl.size,
    getBytes: () => {
        let bytes = 0;
        hashToBlobBytes.forEach(v => { bytes += Number(v) || 0; });
        return bytes;
    },
    clear: () => {
        clearBlobCaches();
        hashToBlobBytes.clear();
    },
    getDetail: () => Array.from(hashToBlobUrl.keys()),
});

function getSettings() {
    return extension_settings[EXT_ID] || {};
}

function ensureHideCodeStyle(enable) {
    const id = 'xiaobaix-hide-code';
    const old = document.getElementById(id);
    if (!enable) {
        old?.remove();
        return;
    }
    if (old) return;
    const hideCodeStyle = document.createElement('style');
    hideCodeStyle.id = id;
    hideCodeStyle.textContent = `
        .xiaobaix-active .mes_text pre { display: none !important; }
        .xiaobaix-active .mes_text pre.xb-show { display: block !important; }
    `;
    document.head.appendChild(hideCodeStyle);
}

function setActiveClass(enable) {
    document.body.classList.toggle('xiaobaix-active', !!enable);
}

function djb2(str) {
    let h = 5381;
    for (let i = 0; i < str.length; i++) {
        h = ((h << 5) + h) ^ str.charCodeAt(i);
    }
    return (h >>> 0).toString(16);
}

function shouldRenderContentByBlock(codeBlock) {
    if (!codeBlock) return false;
    const content = (codeBlock.textContent || '').trim().toLowerCase();
    if (!content) return false;
    return content.includes('<!doctype') || content.includes('<html') || content.includes('<script');
}

function generateUniqueId() {
    return `xiaobaix-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
}

function setIframeBlobHTML(iframe, fullHTML, codeHash) {
    const existing = hashToBlobUrl.get(codeHash);
    if (existing) {
        iframe.src = existing;
        blobUrls.set(iframe, existing);
        return;
    }
    const blob = new Blob([fullHTML], { type: 'text/html' });
    const url = URL.createObjectURL(blob);
    iframe.src = url;
    blobUrls.set(iframe, url);
    hashToBlobUrl.set(codeHash, url);
    try { hashToBlobBytes.set(codeHash, blob.size || 0); } catch {}
    blobLRU.push(codeHash);
    while (blobLRU.length > BLOB_CACHE_LIMIT) {
        const old = blobLRU.shift();
        const u = hashToBlobUrl.get(old);
        hashToBlobUrl.delete(old);
        hashToBlobBytes.delete(old);
        try { URL.revokeObjectURL(u); } catch (e) {}
    }
}

function releaseIframeBlob(iframe) {
    try {
        const url = blobUrls.get(iframe);
        if (url) URL.revokeObjectURL(url);
        blobUrls.delete(iframe);
    } catch (e) {}
}

export function clearBlobCaches() {
    try { xbLog.info(MODULE_ID, '清空 Blob 缓存'); } catch {}
    hashToBlobUrl.forEach(u => { try { URL.revokeObjectURL(u); } catch {} });
    hashToBlobUrl.clear();
    hashToBlobBytes.clear();
    blobLRU.length = 0;
}

function buildResourceHints(html) {
    const urls = Array.from(new Set((html.match(/https?:\/\/[^"'()\s]+/gi) || [])
        .map(u => { try { return new URL(u).origin; } catch { return null; } })
        .filter(Boolean)));
    let hints = "";
    const maxHosts = 6;
    for (let i = 0; i < Math.min(urls.length, maxHosts); i++) {
        const origin = urls[i];
        hints += `<link rel="dns-prefetch" href="${origin}">`;
        hints += `<link rel="preconnect" href="${origin}" crossorigin>`;
    }
    let preload = "";
    const font = (html.match(/https?:\/\/[^"'()\s]+\.(?:woff2|woff|ttf|otf)/i) || [])[0];
    if (font) {
        const type = font.endsWith(".woff2") ? "font/woff2" : font.endsWith(".woff") ? "font/woff" : font.endsWith(".ttf") ? "font/ttf" : "font/otf";
        preload += `<link rel="preload" as="font" href="${font}" type="${type}" crossorigin fetchpriority="high">`;
    }
    const css = (html.match(/https?:\/\/[^"'()\s]+\.css/i) || [])[0];
    if (css) {
        preload += `<link rel="preload" as="style" href="${css}" crossorigin fetchpriority="high">`;
    }
    const img = (html.match(/https?:\/\/[^"'()\s]+\.(?:png|jpg|jpeg|webp|gif|svg)/i) || [])[0];
    if (img) {
        preload += `<link rel="preload" as="image" href="${img}" crossorigin fetchpriority="high">`;
    }
    return hints + preload;
}

function buildWrappedHtml(html) {
    const settings = getSettings();
    const wrapperToggle = settings.wrapperIframe ?? true;
    const origin = typeof location !== 'undefined' && location.origin ? location.origin : '';
    const baseTag = settings.useBlob ? `<base href="${origin}/">` : "";
    const headHints = buildResourceHints(html);
    const vhFix = `<style>html,body{height:auto!important;min-height:0!important;max-height:none!important}.profile-container,[style*="100vh"]{height:auto!important;min-height:600px!important}[style*="height:100%"]{height:auto!important;min-height:100%!important}</style>`;
    
    // 内联脚本，按顺序：wrapper(callGenerate) -> base(高度+STscript)
    const scripts = wrapperToggle 
        ? `<script>${getWrapperScript()}${getIframeBaseScript()}</script>`
        : `<script>${getIframeBaseScript()}</script>`;
    
    if (html.includes('<html') && html.includes('</html')) {
        if (html.includes('<head>')) 
            return html.replace('<head>', `<head>${scripts}${baseTag}${headHints}${vhFix}`);
        if (html.includes('</head>')) 
            return html.replace('</head>', `${scripts}${baseTag}${headHints}${vhFix}</head>`);
        return html.replace('<body', `<head>${scripts}${baseTag}${headHints}${vhFix}</head><body`);
    }
    
    return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
${scripts}
${baseTag}
${headHints}
${vhFix}
<style>html,body{margin:0;padding:0;background:transparent}</style>
</head>
<body>${html}</body></html>`;
}

function getOrCreateWrapper(preEl) {
    let wrapper = preEl.previousElementSibling;
    if (!wrapper || !wrapper.classList.contains('xiaobaix-iframe-wrapper')) {
        wrapper = document.createElement('div');
        wrapper.className = 'xiaobaix-iframe-wrapper';
        wrapper.style.cssText = 'margin:0;';
        preEl.parentNode.insertBefore(wrapper, preEl);
    }
    return wrapper;
}

function registerIframeMapping(iframe, wrapper) {
    const tryMap = () => {
        try {
            if (iframe && iframe.contentWindow) {
                winMap.set(iframe.contentWindow, { iframe, wrapper });
                return true;
            }
        } catch (e) {}
        return false;
    };
    if (tryMap()) return;
    let tries = 0;
    const t = setInterval(() => {
        tries++;
        if (tryMap() || tries > 20) clearInterval(t);
    }, 25);
}

function resolveAvatarUrls() {
    const origin = typeof location !== 'undefined' && location.origin ? location.origin : '';
    const toAbsUrl = (relOrUrl) => {
        if (!relOrUrl) return '';
        const s = String(relOrUrl);
        if (/^(data:|blob:|https?:)/i.test(s)) return s;
        if (s.startsWith('User Avatars/')) {
            return `${origin}/${s}`;
        }
        const encoded = s.split('/').map(seg => encodeURIComponent(seg)).join('/');
        return `${origin}/${encoded.replace(/^\/+/, '')}`;
    };
    const pickSrc = (selectors) => {
        for (const sel of selectors) {
            const el = document.querySelector(sel);
            if (el) {
                const highRes = el.getAttribute('data-izoomify-url');
                if (highRes) return highRes;
                if (el.src) return el.src;
            }
        }
        return '';
    };
    let user = pickSrc([
        '#user_avatar_block img',
        '#avatar_user img',
        '.user_avatar img',
        'img#avatar_user',
        '.st-user-avatar img'
    ]) || default_user_avatar;
    const m = String(user).match(/\/thumbnail\?type=persona&file=([^&]+)/i);
    if (m) {
        user = `User Avatars/${decodeURIComponent(m[1])}`;
    }
    const ctx = getContext?.() || {};
    const chId = ctx.characterId ?? ctx.this_chid;
    const ch = Array.isArray(ctx.characters) ? ctx.characters[chId] : null;
    let char = ch?.avatar || default_avatar;
    if (char && !/^(data:|blob:|https?:)/i.test(char)) {
        char = String(char).includes('/') ? char.replace(/^\/+/, '') : `characters/${char}`;
    }
    return { user: toAbsUrl(user), char: toAbsUrl(char) };
}

function handleIframeMessage(event) {
    const data = event.data || {};
    let rec = winMap.get(event.source);
    
    if (!rec || !rec.iframe) {
        const iframes = document.querySelectorAll('iframe.xiaobaix-iframe');
        for (const iframe of iframes) {
            if (iframe.contentWindow === event.source) {
                rec = { iframe, wrapper: iframe.parentElement };
                winMap.set(event.source, rec);
                break;
            }
        }
    }
    
    if (rec && rec.iframe && typeof data.height === 'number') {
        const next = Math.max(0, Number(data.height) || 0);
        if (next < 1) return;
        const prev = lastHeights.get(rec.iframe) || 0;
        if (!data.force && Math.abs(next - prev) < 1) return;
        if (data.force) {
            lastHeights.set(rec.iframe, next);
            requestAnimationFrame(() => { rec.iframe.style.height = `${next}px`; });
            return;
        }
        pendingHeight = next;
        pendingRec = rec;
        const now = performance.now();
        const dt = now - lastApplyTs;
        if (dt >= 50) {
            lastApplyTs = now;
            const h = pendingHeight, r = pendingRec;
            pendingHeight = null;
            pendingRec = null;
            lastHeights.set(r.iframe, h);
            requestAnimationFrame(() => { r.iframe.style.height = `${h}px`; });
        } else {
            setTimeout(() => {
                if (pendingRec && pendingHeight != null) {
                    lastApplyTs = performance.now();
                    const h = pendingHeight, r = pendingRec;
                    pendingHeight = null;
                    pendingRec = null;
                    lastHeights.set(r.iframe, h);
                    requestAnimationFrame(() => { r.iframe.style.height = `${h}px`; });
                }
            }, Math.max(0, 50 - dt));
        }
        return;
    }
    
    if (data && data.type === 'runCommand') {
        const replyOrigin = (typeof event.origin === 'string' && event.origin) ? event.origin : getTrustedOrigin();
        executeSlashCommand(data.command)
            .then(result => event.source.postMessage({
                source: 'xiaobaix-host',
                type: 'commandResult',
                id: data.id,
                result
            }, replyOrigin))
            .catch(err => event.source.postMessage({
                source: 'xiaobaix-host',
                type: 'commandError',
                id: data.id,
                error: err.message || String(err)
            }, replyOrigin));
        return;
    }
    
    if (data && data.type === 'getAvatars') {
        const replyOrigin = (typeof event.origin === 'string' && event.origin) ? event.origin : getTrustedOrigin();
        try {
            const urls = resolveAvatarUrls();
            event.source?.postMessage({ source: 'xiaobaix-host', type: 'avatars', urls }, replyOrigin);
        } catch (e) {
            event.source?.postMessage({ source: 'xiaobaix-host', type: 'avatars', urls: { user: '', char: '' } }, replyOrigin);
        }
        return;
    }
}

export function renderHtmlInIframe(htmlContent, container, preElement) {
    const settings = getSettings();
    try {
        const originalHash = djb2(htmlContent);
        
        if (settings.variablesCore?.enabled && typeof replaceXbGetVarInString === 'function') {
            try {
                htmlContent = replaceXbGetVarInString(htmlContent);
            } catch (e) {
                console.warn('xbgetvar 宏替换失败:', e);
            }
        }
        
        const iframe = document.createElement('iframe');
        iframe.id = generateUniqueId();
        iframe.className = 'xiaobaix-iframe';
        iframe.style.cssText = 'width:100%;border:none;background:transparent;overflow:hidden;height:0;margin:0;padding:0;display:block;contain:layout paint style;will-change:height;min-height:50px';
        iframe.setAttribute('frameborder', '0');
        iframe.setAttribute('scrolling', 'no');
        iframe.loading = 'eager';
        
        if (settings.sandboxMode) {
            iframe.setAttribute('sandbox', 'allow-scripts');
        }
        
        const wrapper = getOrCreateWrapper(preElement);
        wrapper.querySelectorAll('.xiaobaix-iframe').forEach(old => {
            try { old.src = 'about:blank'; } catch (e) {}
            releaseIframeBlob(old);
            old.remove();
        });
        
        const codeHash = djb2(htmlContent);
        const full = buildWrappedHtml(htmlContent);
        
        if (settings.useBlob) {
            setIframeBlobHTML(iframe, full, codeHash);
        } else {
            iframe.srcdoc = full;
        }
        
        wrapper.appendChild(iframe);
        preElement.classList.remove('xb-show');
        preElement.style.display = 'none';
        registerIframeMapping(iframe, wrapper);
        
        try {
            const targetOrigin = getIframeTargetOrigin(iframe);
            postToIframe(iframe, { type: 'probe' }, null, targetOrigin);
        } catch (e) {}
        preElement.dataset.xbFinal = 'true';
        preElement.dataset.xbHash = originalHash;
        
        return iframe;
    } catch (err) {
        console.error('[iframeRenderer] 渲染失败:', err);
        return null;
    }
}

export function processCodeBlocks(messageElement, forceFinal = true) {
    const settings = getSettings();
    if (!settings.enabled) return;
    if (settings.renderEnabled === false) return;
    
    try {
        const codeBlocks = messageElement.querySelectorAll('pre > code');
        const ctx = getContext();
        const lastId = ctx.chat?.length - 1;
        const mesEl = messageElement.closest('.mes');
        const mesId = mesEl ? Number(mesEl.getAttribute('mesid')) : null;
        
        if (isGenerating && mesId === lastId && !forceFinal) return;
        
        codeBlocks.forEach(codeBlock => {
            const preElement = codeBlock.parentElement;
            const should = shouldRenderContentByBlock(codeBlock);
            const html = codeBlock.textContent || '';
            const hash = djb2(html);
            const isFinal = preElement.dataset.xbFinal === 'true';
            const same = preElement.dataset.xbHash === hash;
            
            if (isFinal && same) return;
            
            if (should) {
                renderHtmlInIframe(html, preElement.parentNode, preElement);
            } else {
                preElement.classList.add('xb-show');
                preElement.removeAttribute('data-xbfinal');
                preElement.removeAttribute('data-xbhash');
                preElement.style.display = '';
            }
            preElement.dataset.xiaobaixBound = 'true';
        });
    } catch (err) {
        console.error('[iframeRenderer] processCodeBlocks 失败:', err);
    }
}

export function processExistingMessages() {
    const settings = getSettings();
    if (!settings.enabled) return;
    document.querySelectorAll('.mes_text').forEach(el => processCodeBlocks(el, true));
    try { shrinkRenderedWindowFull(); } catch (e) {}
}

export function processMessageById(messageId, forceFinal = true) {
    const messageElement = document.querySelector(`div.mes[mesid="${messageId}"] .mes_text`);
    if (!messageElement) return;
    processCodeBlocks(messageElement, forceFinal);
    try { shrinkRenderedWindowForLastMessage(); } catch (e) {}
}

export function invalidateMessage(messageId) {
    const el = document.querySelector(`div.mes[mesid="${messageId}"] .mes_text`);
    if (!el) return;
    el.querySelectorAll('.xiaobaix-iframe-wrapper').forEach(w => {
        w.querySelectorAll('.xiaobaix-iframe').forEach(ifr => {
            try { ifr.src = 'about:blank'; } catch (e) {}
            releaseIframeBlob(ifr);
        });
        w.remove();
    });
    el.querySelectorAll('pre').forEach(pre => {
        pre.classList.remove('xb-show');
        pre.removeAttribute('data-xbfinal');
        pre.removeAttribute('data-xbhash');
        delete pre.dataset.xbFinal;
        delete pre.dataset.xbHash;
        pre.style.display = '';
        delete pre.dataset.xiaobaixBound;
    });
}

export function invalidateAll() {
    document.querySelectorAll('.xiaobaix-iframe-wrapper').forEach(w => {
        w.querySelectorAll('.xiaobaix-iframe').forEach(ifr => {
            try { ifr.src = 'about:blank'; } catch (e) {}
            releaseIframeBlob(ifr);
        });
        w.remove();
    });
    document.querySelectorAll('.mes_text pre').forEach(pre => {
        pre.classList.remove('xb-show');
        pre.removeAttribute('data-xbfinal');
        pre.removeAttribute('data-xbhash');
        delete pre.dataset.xbFinal;
        delete pre.dataset.xbHash;
        delete pre.dataset.xiaobaixBound;
        pre.style.display = '';
    });
    clearBlobCaches();
    winMap.clear();
    lastHeights = new WeakMap();
}

function shrinkRenderedWindowForLastMessage() {
    const settings = getSettings();
    if (!settings.enabled) return;
    if (settings.renderEnabled === false) return;
    const max = Number.isFinite(settings.maxRenderedMessages) && settings.maxRenderedMessages > 0
        ? settings.maxRenderedMessages
        : 0;
    if (max <= 0) return;
    const ctx = getContext?.();
    const chatArr = ctx?.chat;
    if (!Array.isArray(chatArr) || chatArr.length === 0) return;
    const lastId = chatArr.length - 1;
    if (lastId < 0) return;
    const keepFrom = Math.max(0, lastId - max + 1);
    const mesList = document.querySelectorAll('div.mes');
    for (const mes of mesList) {
        const mesIdAttr = mes.getAttribute('mesid');
        if (mesIdAttr == null) continue;
        const mesId = Number(mesIdAttr);
        if (!Number.isFinite(mesId)) continue;
        if (mesId >= keepFrom) break;
        const mesText = mes.querySelector('.mes_text');
        if (!mesText) continue;
        mesText.querySelectorAll('.xiaobaix-iframe-wrapper').forEach(w => {
            w.querySelectorAll('.xiaobaix-iframe').forEach(ifr => {
                try { ifr.src = 'about:blank'; } catch (e) {}
                releaseIframeBlob(ifr);
            });
            w.remove();
        });
        mesText.querySelectorAll('pre[data-xiaobaix-bound="true"]').forEach(pre => {
            pre.classList.remove('xb-show');
            pre.removeAttribute('data-xbfinal');
            pre.removeAttribute('data-xbhash');
            delete pre.dataset.xbFinal;
            delete pre.dataset.xbHash;
            delete pre.dataset.xiaobaixBound;
            pre.style.display = '';
        });
    }
}

function shrinkRenderedWindowFull() {
    const settings = getSettings();
    if (!settings.enabled) return;
    if (settings.renderEnabled === false) return;
    const max = Number.isFinite(settings.maxRenderedMessages) && settings.maxRenderedMessages > 0
        ? settings.maxRenderedMessages
        : 0;
    if (max <= 0) return;
    const ctx = getContext?.();
    const chatArr = ctx?.chat;
    if (!Array.isArray(chatArr) || chatArr.length === 0) return;
    const lastId = chatArr.length - 1;
    const keepFrom = Math.max(0, lastId - max + 1);
    const mesList = document.querySelectorAll('div.mes');
    for (const mes of mesList) {
        const mesIdAttr = mes.getAttribute('mesid');
        if (mesIdAttr == null) continue;
        const mesId = Number(mesIdAttr);
        if (!Number.isFinite(mesId)) continue;
        if (mesId >= keepFrom) continue;
        const mesText = mes.querySelector('.mes_text');
        if (!mesText) continue;
        mesText.querySelectorAll('.xiaobaix-iframe-wrapper').forEach(w => {
            w.querySelectorAll('.xiaobaix-iframe').forEach(ifr => {
                try { ifr.src = 'about:blank'; } catch (e) {}
                releaseIframeBlob(ifr);
            });
            w.remove();
        });
        mesText.querySelectorAll('pre[data-xiaobaix-bound="true"]').forEach(pre => {
            pre.classList.remove('xb-show');
            pre.removeAttribute('data-xbfinal');
            pre.removeAttribute('data-xbhash');
            delete pre.dataset.xbFinal;
            delete pre.dataset.xbHash;
            delete pre.dataset.xiaobaixBound;
            pre.style.display = '';
        });
    }
}

let messageListenerBound = false;

export function initRenderer() {
    const settings = getSettings();
    if (!settings.enabled) return;
    
    try { xbLog.info(MODULE_ID, 'initRenderer'); } catch {}
    
    if (settings.renderEnabled !== false) {
        ensureHideCodeStyle(true);
        setActiveClass(true);
    }
    
    events.on(event_types.GENERATION_STARTED, () => {
        isGenerating = true;
    });
    
    events.on(event_types.GENERATION_ENDED, () => {
        isGenerating = false;
        const ctx = getContext();
        const lastId = ctx.chat?.length - 1;
        if (lastId != null && lastId >= 0) {
            setTimeout(() => {
                processMessageById(lastId, true);
            }, 60);
        }
    });
    
    events.on(event_types.MESSAGE_RECEIVED, (data) => {
        setTimeout(() => {
            const messageId = typeof data === 'object' ? data.messageId : data;
            if (messageId != null) {
                processMessageById(messageId, true);
            }
        }, 300);
    });
    
    events.on(event_types.MESSAGE_UPDATED, (data) => {
        const messageId = typeof data === 'object' ? data.messageId : data;
        if (messageId != null) {
            processMessageById(messageId, true);
        }
    });
    
    events.on(event_types.MESSAGE_EDITED, (data) => {
        const messageId = typeof data === 'object' ? data.messageId : data;
        if (messageId != null) {
            processMessageById(messageId, true);
        }
    });
    
    events.on(event_types.MESSAGE_DELETED, (data) => {
        const messageId = typeof data === 'object' ? data.messageId : data;
        if (messageId != null) {
            invalidateMessage(messageId);
        }
    });
    
    events.on(event_types.MESSAGE_SWIPED, (data) => {
        setTimeout(() => {
            const messageId = typeof data === 'object' ? data.messageId : data;
            if (messageId != null) {
                processMessageById(messageId, true);
            }
        }, 10);
    });
    
    events.on(event_types.USER_MESSAGE_RENDERED, (data) => {
        setTimeout(() => {
            const messageId = typeof data === 'object' ? data.messageId : data;
            if (messageId != null) {
                processMessageById(messageId, true);
            }
        }, 10);
    });
    
    events.on(event_types.CHARACTER_MESSAGE_RENDERED, (data) => {
        setTimeout(() => {
            const messageId = typeof data === 'object' ? data.messageId : data;
            if (messageId != null) {
                processMessageById(messageId, true);
            }
        }, 10);
    });
    
    events.on(event_types.CHAT_CHANGED, () => {
        isGenerating = false;
        invalidateAll();
        setTimeout(() => {
            processExistingMessages();
        }, 100);
    });
    
    if (!messageListenerBound) {
        // eslint-disable-next-line no-restricted-syntax -- message bridge for iframe renderers.
        window.addEventListener('message', handleIframeMessage);
        messageListenerBound = true;
    }
    
    setTimeout(processExistingMessages, 100);
}

export function cleanupRenderer() {
    try { xbLog.info(MODULE_ID, 'cleanupRenderer'); } catch {}
    events.cleanup();
    if (messageListenerBound) {
        window.removeEventListener('message', handleIframeMessage);
        messageListenerBound = false;
    }
    
    ensureHideCodeStyle(false);
    setActiveClass(false);
    
    document.querySelectorAll('pre[data-xiaobaix-bound="true"]').forEach(pre => {
        pre.classList.remove('xb-show');
        pre.removeAttribute('data-xbfinal');
        pre.removeAttribute('data-xbhash');
        delete pre.dataset.xbFinal;
        delete pre.dataset.xbHash;
        pre.style.display = '';
        delete pre.dataset.xiaobaixBound;
    });
    
    invalidateAll();
    isGenerating = false;
    pendingHeight = null;
    pendingRec = null;
    lastApplyTs = 0;
}

export function isCurrentlyGenerating() {
    return isGenerating;
}

export { shrinkRenderedWindowFull, shrinkRenderedWindowForLastMessage };
