(function () {
    "use strict";

    const DEFAULT_DEMO_RESPONSE = `Here's a sample HTML artifact:\n\n:::artifact{identifier="sample-page" type="text/html" title="Sample HTML Page"}\n<!DOCTYPE html>\n<html>\n<head>\n    <title>Sample Page</title>\n    <style>\n        body { font-family: Arial, sans-serif; padding: 20px; background: linear-gradient(45deg, #667eea, #764ba2); color: white; }\n        .container { max-width: 600px; margin: 0 auto; text-align: center; }\n        .card { background: rgba(255,255,255,0.1); padding: 30px; border-radius: 15px; backdrop-filter: blur(10px); }\n    </style>\n</head>\n<body>\n    <div class="container">\n        <div class="card">\n            <h1>Hello from Tenzin!</h1>\n            <p>This is a sample HTML artifact that demonstrates the artifact system.</p>\n            <button onclick="alert('Hello!')">Click me!</button>\n        </div>\n    </div>\n</body>\n</html>\n:::\n\nThis demonstrates how artifacts work in the chat interface.`;

    const DEFAULT_STORAGE_PREFIX = "customdhx:rag-chat";
    const EVENT_HANDLERS = new WeakMap();

    function createUniqueId(prefix) {
        const idPrefix = prefix || "rag";
        return `${idPrefix}-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
    }

    function formatTime(date) {
        return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
    }

    function clamp(value, min, max) {
        if (Number.isNaN(value)) return min;
        return Math.max(min, Math.min(max, value));
    }

    function ensureMaterialIcons() {
        if (document.getElementById("ragchat-material-icons")) {
            return;
        }
        const link = document.createElement("link");
        link.id = "ragchat-material-icons";
        link.rel = "stylesheet";
        link.href = "https://fonts.googleapis.com/icon?family=Material+Icons";
        document.head.appendChild(link);
    }

    function escapeHtml(value) {
        return (value ?? "").replace(/[&<>"']/g, (char) => {
            switch (char) {
                case "&":
                    return "&amp;";
                case "<":
                    return "&lt;";
                case ">":
                    return "&gt;";
                case '"':
                    return "&quot;";
                case "'":
                    return "&#39;";
                default:
                    return char;
            }
        });
    }

    function renderInlineMarkdown(text) {
        let html = escapeHtml(text);
        html = html.replace(/\[([^\]]+)\]\(([^\s)]+)(?:\s+"([^"]+)")?\)/g, (_match, label, href, title) => {
            const safeHref = escapeHtml(href);
            const safeLabel = escapeHtml(label);
            const safeTitle = title ? ` title=\"${escapeHtml(title)}\"` : "";
            return `<a href="${safeHref}" target="_blank" rel="noopener noreferrer"${safeTitle}>${safeLabel}</a>`;
        });
        html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
        html = html.replace(/__([^_]+)__/g, "<strong>$1</strong>");
        html = html.replace(/(?<!_)_([^_]+)_(?!_)/g, "<em>$1</em>");
        html = html.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "<em>$1</em>");
        html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
        return html;
    }

    function renderBlockMarkdown(text) {
        const lines = text.split(/\n/);
        const blocks = [];
        let listBuffer = [];
        let listType = null;
        let paragraph = [];

        const flushParagraph = () => {
            if (!paragraph.length) return;
            const content = paragraph.map((line) => renderInlineMarkdown(line)).join("<br>");
            blocks.push(`<p>${content}</p>`);
            paragraph = [];
        };

        const flushList = () => {
            if (!listBuffer.length) return;
            const items = listBuffer.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join("");
            const tag = listType === "ol" ? "ol" : "ul";
            blocks.push(`<${tag}>${items}</${tag}>`);
            listBuffer = [];
            listType = null;
        };

        lines.forEach((rawLine) => {
            const line = rawLine;
            const trimmed = line.trim();
            if (!trimmed) {
                flushList();
                flushParagraph();
                return;
            }

            const headingMatch = trimmed.match(/^(#{1,6})\s+(.*)$/);
            const orderedMatch = trimmed.match(/^\d+\.\s+(.*)$/);
            const unorderedMatch = trimmed.match(/^[*-]\s+(.*)$/);
            const blockquoteMatch = trimmed.match(/^>\s+(.*)$/);

            if (headingMatch) {
                flushList();
                flushParagraph();
                const level = headingMatch[1].length;
                const content = renderInlineMarkdown(headingMatch[2]);
                blocks.push(`<h${level}>${content}</h${level}>`);
                return;
            }

            if (blockquoteMatch) {
                flushList();
                flushParagraph();
                blocks.push(`<blockquote>${renderInlineMarkdown(blockquoteMatch[1])}</blockquote>`);
                return;
            }

            if (orderedMatch) {
                flushParagraph();
                if (listType && listType !== "ol") {
                    flushList();
                }
                listType = "ol";
                listBuffer.push(orderedMatch[1]);
                return;
            }

            if (unorderedMatch) {
                flushParagraph();
                if (listType && listType !== "ul") {
                    flushList();
                }
                listType = "ul";
                listBuffer.push(unorderedMatch[1]);
                return;
            }

            paragraph.push(line);
        });

        flushList();
        flushParagraph();

        return blocks.join("");
    }

    function basicMarkdown(input) {
        if (!input) return "";
        const fenceRegex = /```([\s\S]*?)```/g;
        const segments = [];
        let lastIndex = 0;
        let match;
        while ((match = fenceRegex.exec(input)) !== null) {
            if (match.index > lastIndex) {
                segments.push({ type: "text", value: input.slice(lastIndex, match.index) });
            }
            segments.push({ type: "code", value: match[1] });
            lastIndex = match.index + match[0].length;
        }
        if (lastIndex < input.length) {
            segments.push({ type: "text", value: input.slice(lastIndex) });
        }

        return segments.map((segment) => {
            if (segment.type === "code") {
                return `<pre><code>${escapeHtml(segment.value)}</code></pre>`;
            }
            return renderBlockMarkdown(segment.value);
        }).join("");
    }

    function renderMarkdown(text) {
        if (!text) return "";
        if (typeof marked !== "undefined" && typeof marked.parse === "function") {
            try {
                return marked.parse(text);
            } catch (error) {
                console.warn("[ChatWidget] markdown parse failed", error);
            }
        }
        return basicMarkdown(text);
    }

    function injectStyles() {
        if (document.getElementById("ragchat-widget-styles")) {
            return;
        }
        const style = document.createElement("style");
        style.id = "ragchat-widget-styles";
        style.textContent = `
            .rag-container { display: flex; height: 100%; width: 100%; background: #f6f8fb; color: #1f2937; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
            .rag-container.rag-dark { background: #1e2534; color: #e2e8f0; }
            .rag-sidebar { width: 260px; min-width: 220px; max-width: 360px; border-right: 1px solid rgba(15, 23, 42, 0.08); background: rgba(255,255,255,0.82); backdrop-filter: blur(12px); display: flex; flex-direction: column; position: relative; overflow: visible; transition: width 0.2s ease; }
            .rag-dark .rag-sidebar { background: rgba(15,23,42,0.62); border-right-color: rgba(226,232,240,0.1); }
            .rag-sidebar.collapsed { width: 64px; min-width: 64px; max-width: 64px; padding-right: 6px; border-right: none; }
            .rag-sidebar.collapsed .sidebar-controls { flex-direction: column; align-items: center; gap: 8px; padding: 16px 6px 8px; }
            .rag-sidebar.collapsed .sidebar-controls .sidebar-btn { width: 44px; height: 44px; }
            .rag-sidebar.collapsed .sidebar-controls .sidebar-btn:not([data-action='toggle-sidebar']) { display: none; }
            .rag-sidebar.collapsed .sidebar-controls .tooltip { display: none; }
            .sidebar-controls { display: flex; gap: 8px; padding: 16px; align-items: center; }
            .sidebar-btn { position: relative; background: rgba(59,130,246,0.12); border: none; border-radius: 12px; width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: inherit; }
            .sidebar-btn:hover { background: rgba(59,130,246,0.22); }
            .sidebar-btn .tooltip { position: absolute; background: rgba(15,23,42,0.85); color: #fff; font-size: 11px; padding: 4px 8px; border-radius: 6px; bottom: -32px; opacity: 0; pointer-events: none; transform: translateY(8px); transition: opacity 0.15s ease, transform 0.15s ease; white-space: nowrap; }
            .sidebar-btn:hover .tooltip { opacity: 1; transform: translateY(0); }
            .rag-chat-list { flex: 1; overflow-y: auto; padding: 0 12px 16px; display: flex; flex-direction: column; gap: 6px; }
            .rag-sidebar.collapsed .rag-chat-list { padding: 8px 6px 12px; align-items: center; gap: 10px; }
            .rag-chat-list::before { content: ""; position: absolute; top: 0; left: 12px; right: 12px; height: 1px; background: rgba(15,23,42,0.08); }
            .rag-chat-list { position: relative; }
            .rag-chat-item { position: relative; padding: 12px 14px; border-radius: 12px; background: transparent; cursor: pointer; display: flex; align-items: center; gap: 12px; transition: background 0.15s ease, transform 0.15s ease; border-bottom: 1px solid rgba(15,23,42,0.06); }
            .rag-chat-item:last-child { border-bottom: none; }
            .rag-chat-item .chat-title { flex: 1; font-size: 14px; font-weight: 500; }
            .rag-chat-item .chat-model { font-size: 11px; opacity: 0.6; }
            .rag-chat-item .chat-delete-btn { background: none; border: none; cursor: pointer; opacity: 0; transform: translateX(4px); transition: opacity 0.15s ease, transform 0.15s ease; color: inherit; display: flex; align-items: center; justify-content: center; border-radius: 8px; width: 28px; height: 28px; }
            .rag-chat-item:hover { background: rgba(59,130,246,0.1); }
            .rag-chat-item.active { background: rgba(59,130,246,0.2); }
            .rag-chat-item:hover .chat-delete-btn { opacity: 1; transform: translateX(0); }
            .rag-chat-item .chat-badge { min-width: 20px; height: 20px; border-radius: 999px; background: #ef4444; color: #fff; font-size: 11px; font-weight: 600; display: inline-flex; align-items: center; justify-content: center; padding: 0 6px; margin-left: 6px; }
            .rag-chat-item.active .chat-badge { background: #f97316; }
            .rag-sidebar.collapsed .chat-badge { position: absolute; top: -4px; right: -4px; margin: 0; min-width: 18px; height: 18px; font-size: 10px; }
            .rag-chat-item[data-initial]::before { content: attr(data-initial); font-weight: 600; width: 32px; height: 32px; border-radius: 10px; background: rgba(59,130,246,0.18); display: flex; align-items: center; justify-content: center; }
            .rag-sidebar.collapsed .rag-chat-item { justify-content: center; width: 44px; height: 44px; padding: 0; border-radius: 14px; position: relative; }
            .rag-sidebar.collapsed .rag-chat-item .chat-title,
            .rag-sidebar.collapsed .rag-chat-item .chat-model,
            .rag-sidebar.collapsed .rag-chat-item .chat-delete-btn { display: none; }
            .rag-sidebar.collapsed .rag-chat-item::before { margin: 0; width: 36px; height: 36px; border-radius: 12px; font-size: 15px; }
            .rag-chat-item .tooltip { position: absolute; left: 54px; top: 50%; transform: translateY(-50%) scale(0.95); background: rgba(15,23,42,0.9); color: #fff; padding: 6px 10px; border-radius: 8px; font-size: 12px; pointer-events: none; opacity: 0; transition: opacity 0.15s ease, transform 0.15s ease; white-space: nowrap; box-shadow: 0 8px 20px rgba(15,23,42,0.2); visibility: hidden; }
            .rag-dark .rag-chat-item .tooltip { background: rgba(226,232,240,0.92); color: #111827; }
            .rag-sidebar.collapsed .rag-chat-item:hover .tooltip { opacity: 1; transform: translateY(-50%) scale(1); visibility: visible; }
            .rag-sidebar:not(.collapsed) .rag-chat-item .tooltip { display: none; }
            .rag-main { flex: 1; position: relative; display: flex; flex-direction: column; min-width: 0; }
            .rag-header { padding: 18px 24px 14px; display: flex; justify-content: space-between; align-items: flex-start; }
            .rag-header-left { display: flex; flex-direction: column; gap: 4px; }
            .rag-agent-name { font-size: 20px; font-weight: 600; }
            .rag-agent-subtitle { font-size: 13px; opacity: 0.65; }
            .rag-header-right { display: flex; align-items: center; gap: 10px; }
            .rag-header-right select { padding: 8px 12px; border: 1px solid rgba(15,23,42,0.12); border-radius: 10px; background: rgba(255,255,255,0.85); font-size: 13px; min-width: 180px; }
            .rag-chat-scroll { flex: 1; overflow-y: auto; padding: 0 24px 16px; }
            .rag-chat-container { display: flex; flex-direction: column; gap: 16px; padding-bottom: 80px; }
            .rag-message { position: relative; display: flex; flex-direction: column; gap: 0; max-width: 76%; min-width: 240px; align-items: flex-start; padding: 0; background: transparent; border: none; box-shadow: none; }
            .rag-message:hover .message-bubble { transform: translateY(-1px); box-shadow: 0 22px 44px rgba(15,23,42,0.12); }
            .rag-user-message { align-self: flex-end; align-items: flex-end; }
            .rag-message .message-header { display: flex; align-items: center; gap: 10px; font-size: 11px; letter-spacing: 0.08em; padding: 0 4px 6px; color: rgba(15,23,42,0.55); width: 100%; }
            .rag-user-message .message-header { color: rgba(11,30,63,0.6); justify-content: flex-end; }
            .rag-message .message-name { font-weight: 600; text-transform: uppercase; }
            .rag-message .message-timestamp { font-weight: 500; opacity: 0.65; font-size: 11px; text-transform: uppercase; }
            .rag-message .message-timestamp::before { content: "\u00B7"; margin: 0 0 0 0.45em; opacity: 0.6; }
            .message-timestamp.is-hidden { display: none; }
            .message-tray { width: 100%; display: flex; justify-content: flex-end; align-items: center; gap: 4px; padding: 2px 4px 0; }
            .message-copy-btn { border: none; background: rgba(148,163,184,0.2); color: inherit; width: 26px; height: 26px; border-radius: 9px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; transition: background 0.2s ease, transform 0.2s ease; }
            .message-copy-btn .material-icons { font-size: 16px; }
            .message-copy-btn:hover { background: rgba(59,130,246,0.28); transform: translateY(-1px); }
            .rag-user-message .message-copy-btn { background: rgba(15,23,42,0.1); color: #0b1e3f; }
            .rag-user-message .message-copy-btn:hover { background: rgba(15,23,42,0.18); }
            .message-bubble { width: 100%; padding: 16px 20px 18px; border-radius: 20px; background: rgba(255,255,255,0.98); color: #0f172a; border: 1px solid rgba(15,23,42,0.06); box-shadow: 0 18px 38px rgba(15,23,42,0.08); transition: transform 0.2s ease, box-shadow 0.2s ease; }
            .rag-user-message .message-bubble { background: linear-gradient(145deg, #d0e1ff 0%, #bccdfd 55%, #aebefd 100%); color: #0b1e3f; border: 1px solid rgba(59,130,246,0.2); box-shadow: 0 18px 32px rgba(79,70,229,0.18); }
            .rag-message .message-content { color: inherit; line-height: 1.65; }
            .rag-user-message .message-content { color: #0b1e3f; }
            .rag-user-message .message-content a { color: #1d4ed8; }
            .message-bubble pre { overflow-x: auto; border-radius: 14px; background: rgba(15,23,42,0.06); padding: 12px 16px; color: inherit; }
            .message-bubble code { word-break: break-word; }
            .rag-user-message .message-bubble pre { background: rgba(15,23,42,0.1); color: #0b1e3f; }
            .rag-input-container { padding: 16px 24px 24px; }
            .rag-input-form { display: flex; gap: 12px; align-items: flex-end; background: rgba(255,255,255,0.95); border-radius: 18px; border: 1px solid rgba(15,23,42,0.08); padding: 12px 16px; box-shadow: 0 12px 40px rgba(15,23,42,0.12); }
            .rag-input-form textarea { flex: 0 0 auto; width: 100%; border: none; resize: none; background: transparent; font-size: 15px; line-height: 1.5; min-height: 44px; overflow-y: hidden; box-sizing: border-box; }
            .rag-input-form textarea:focus { outline: none; }
            .rag-input-form button { border: none; padding: 12px 20px; border-radius: 12px; background: linear-gradient(135deg, #2563eb, #7c3aed); color: white; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
            .rag-input-form button:disabled { opacity: 0.5; cursor: not-allowed; }
            .artifact-panel { position: absolute; top: 0; right: 0; height: 100%; width: 0; background: rgba(15,23,42,0.95); color: #e2e8f0; box-shadow: -12px 0 32px rgba(15,23,42,0.45); display: flex; flex-direction: column; transition: width 0.2s ease, transform 0.2s ease; overflow: hidden; }
            .artifact-panel.visible { width: 40%; min-width: 320px; }
            .artifact-resize-handle { position: absolute; left: -4px; top: 0; bottom: 0; width: 8px; cursor: col-resize; }
            .artifact-header { padding: 18px 20px 12px; border-bottom: 1px solid rgba(148,163,184,0.22); display: flex; flex-direction: column; gap: 12px; }
            .artifact-title { font-size: 18px; font-weight: 600; }
            .artifact-tabs { display: flex; gap: 8px; }
            .artifact-tab { background: rgba(255,255,255,0.08); border: none; border-radius: 10px; padding: 8px 14px; color: inherit; cursor: pointer; font-size: 13px; }
            .artifact-tab.active { background: rgba(255,255,255,0.22); }
            .artifact-body { flex: 1; display: flex; flex-direction: column; gap: 12px; padding: 14px 20px 20px; overflow: hidden; }
            .artifact-code { flex: 1; display: none; background: rgba(15,23,42,0.85); border-radius: 12px; padding: 14px; overflow: auto; font-family: "Fira Code", monospace; font-size: 13px; }
            .artifact-code.visible { display: block; }
            .artifact-preview { flex: 1; display: none; border-radius: 12px; overflow: hidden; background: rgba(255,255,255,0.95); }
            .artifact-preview.visible { display: block; }
            .artifact-preview iframe { width: 100%; height: 100%; border: none; }
            .artifact-actions { padding: 0 20px 20px; display: flex; gap: 10px; }
            .artifact-actions button { flex: 1; border: none; border-radius: 10px; padding: 10px 12px; font-size: 13px; background: rgba(59,130,246,0.18); color: inherit; cursor: pointer; }
            .artifact-actions button:hover { background: rgba(59,130,246,0.28); }
            .artifact-icon { display: inline-flex; align-items: center; gap: 6px; background: rgba(59,130,246,0.15); padding: 6px 10px; border-radius: 999px; font-size: 13px; cursor: pointer; margin: 6px 6px 0 0; }
            .artifact-icon span.material-icons { font-size: 18px; }
            .rag-main.with-artifact { margin-right: 40%; }
            @media (max-width: 1200px) {
                .artifact-panel.visible { width: 48%; }
                .rag-main.with-artifact { margin-right: 48%; }
            }
            @media (max-width: 960px) {
                .artifact-panel { position: fixed; width: 100%; max-width: none; height: 60%; bottom: 0; top: auto; border-top: 1px solid rgba(148,163,184,0.25); }
                .artifact-panel.visible { width: 100%; }
                .rag-main.with-artifact { margin-right: 0; }
                .artifact-resize-handle { display: none; }
            }
        `;
        document.head.appendChild(style);
    }

    class EventBus {
        constructor() {
            this._listeners = new Map();
        }

        on(event, handler) {
            if (!this._listeners.has(event)) {
                this._listeners.set(event, new Set());
            }
            this._listeners.get(event).add(handler);
        }

        off(event, handler) {
            if (!this._listeners.has(event)) return;
            this._listeners.get(event).delete(handler);
        }

        emit(event, payload) {
            const eventObj = { event, payload, handled: false, defaultPrevented: false, results: [] };
            if (!this._listeners.has(event)) {
                return eventObj;
            }
            const listeners = Array.from(this._listeners.get(event));
            listeners.forEach((listener) => {
                try {
                    const result = listener(payload);
                    eventObj.results.push(result);
                    if (result === true) {
                        eventObj.handled = true;
                    }
                } catch (error) {
                    console.error(`[ChatWidget] listener for ${event} failed`, error);
                }
            });
            if (!eventObj.handled && eventObj.results.some((value) => value === true)) {
                eventObj.handled = true;
            }
            return eventObj;
        }
    }

    class TenzinChatApp {
        constructor(root, options, host) {
            this.root = root;
            this.host = host;
            this.options = Object.assign({
                agent: { name: "Assistant", subtitle: "" },
                demoResponse: DEFAULT_DEMO_RESPONSE,
                storageKey: null,
                enableArtifacts: true,
                autoAppendUserMessages: true,
                inputPlaceholder: "Ask a question…",
                sendButtonText: "Send",
            }, options || {});

            this._storagePrefix = this.options.storageKey || `${DEFAULT_STORAGE_PREFIX}:${createUniqueId("instance")}`;
            this._storageKeys = {
                chats: `${this._storagePrefix}:chats`,
                activeChatId: `${this._storagePrefix}:active`,
                artifactPanelWidth: `${this._storagePrefix}:artifactWidth`,
                sidebarCollapsed: `${this._storagePrefix}:sidebar`,
                isDarkMode: `${this._storagePrefix}:darkMode`,
            };

            this.chats = [];
            this.activeChatId = null;
            this.availableModels = [];
            this.currentArtifact = null;
            this.streamContext = {
                target: null,
                messageId: null,
                buffer: "",
                isRedirecting: false,
                completedMessageId: null,
                messageArtifactCount: 0,
            };
            this.isArtifactResizing = false;
            this.artifactPanelWidth = localStorage.getItem(this._storageKeys.artifactPanelWidth) || (this.options.artifactPanelWidth ? `${this.options.artifactPanelWidth}px` : "40%");
            this.sidebarCollapsed = localStorage.getItem(this._storageKeys.sidebarCollapsed) === "true";
            this.isDarkMode = localStorage.getItem(this._storageKeys.isDarkMode) === "true";
            this._messageMap = new Map();
            this._artifactMap = new Map();
            this._pendingMessages = [];
            this._pendingStreams = [];
            this._boundHandlers = [];

            this.ids = {
                container: createUniqueId("rag-container"),
                sidebar: createUniqueId("rag-sidebar"),
                chatList: createUniqueId("rag-chat-list"),
                chatContainer: createUniqueId("rag-chat-container"),
                chatScroll: createUniqueId("rag-chat-scroll"),
                inputForm: createUniqueId("rag-input-form"),
                queryInput: createUniqueId("rag-query"),
                sendButton: createUniqueId("rag-send"),
                modelSelect: createUniqueId("rag-model-select"),
                mainContent: createUniqueId("rag-main"),
                artifactPanel: createUniqueId("artifact-panel"),
                artifactResize: createUniqueId("artifact-resize"),
                artifactTitle: createUniqueId("artifact-title"),
                artifactCode: createUniqueId("artifact-code"),
                artifactCodeContent: createUniqueId("artifact-code-content"),
                artifactPreview: createUniqueId("artifact-preview"),
                artifactIframe: createUniqueId("artifact-iframe"),
                artifactCopy: createUniqueId("artifact-copy"),
                artifactDownload: createUniqueId("artifact-download"),
                artifactClose: createUniqueId("artifact-close"),
                artifactTabCode: createUniqueId("artifact-tab-code"),
                artifactTabPreview: createUniqueId("artifact-tab-preview"),
                sidebarControls: createUniqueId("sidebar-controls"),
                composerHelp: createUniqueId("composer-help"),
                agentName: createUniqueId("agent-name"),
                agentSubtitle: createUniqueId("agent-subtitle"),
            };

            this._renderShell();
            this._cacheDom();
            this._bindEvents();
            this._initializeState();
        }

        // -----------------------------------------------------------------
        // Initialization helpers
        // -----------------------------------------------------------------

        _renderShell() {
            const agentName = (this.options.agent && this.options.agent.name) || "Assistant";
            const agentSubtitle = (this.options.agent && this.options.agent.subtitle) || "";
            const composerHelp = this.options.composerHelpText || "Shift+Enter for newline";

            this.root.innerHTML = `
                <div class="rag-container${this.isDarkMode ? " rag-dark" : ""}" id="${this.ids.container}">
                    <div class="rag-sidebar${this.sidebarCollapsed ? " collapsed" : ""}" id="${this.ids.sidebar}">
                        <div class="sidebar-controls" id="${this.ids.sidebarControls}">
                            <button class="sidebar-btn" data-action="new-chat">
                                <span class="material-icons">add</span>
                                <div class="tooltip">New chat</div>
                            </button>
                            <button class="sidebar-btn" data-action="clear-chats">
                                <span class="material-icons">delete</span>
                                <div class="tooltip">Clear chats</div>
                            </button>
                            <button class="sidebar-btn" data-action="toggle-dark">
                                <span class="material-icons">dark_mode</span>
                                <div class="tooltip">Toggle theme</div>
                            </button>
                            <button class="sidebar-btn" data-action="toggle-sidebar">
                                <span class="material-icons">${this.sidebarCollapsed ? "menu" : "menu_open"}</span>
                                <div class="tooltip">${this.sidebarCollapsed ? "Expand" : "Collapse"}</div>
                            </button>
                        </div>
                        <div class="rag-chat-list" id="${this.ids.chatList}"></div>
                    </div>
                    <div class="rag-main${this.artifactPanelWidth ? "" : ""}" id="${this.ids.mainContent}">
                        <div class="rag-header">
                            <div class="rag-header-left">
                                <div class="rag-agent-name" id="${this.ids.agentName}">${agentName}</div>
                                <div class="rag-agent-subtitle" id="${this.ids.agentSubtitle}">${agentSubtitle || ""}</div>
                            </div>
                            <div class="rag-header-right">
                                <select id="${this.ids.modelSelect}">
                                    <option>Loading models...</option>
                                </select>
                            </div>
                        </div>
                        <div class="rag-chat-scroll" id="${this.ids.chatScroll}">
                            <div class="rag-chat-container" id="${this.ids.chatContainer}"></div>
                        </div>
                        <div class="rag-input-container">
                            <form class="rag-input-form" id="${this.ids.inputForm}">
                                <div style="flex:1; display:flex; flex-direction:column;">
                                    <textarea id="${this.ids.queryInput}" placeholder="${this.options.inputPlaceholder || "Ask something..."}"></textarea>
                                    <div class="composer-help" id="${this.ids.composerHelp}">${composerHelp || ""}</div>
                                </div>
                                <button type="submit" id="${this.ids.sendButton}">
                                    <span class="material-icons">send</span>
                                    ${this.options.sendButtonText || "Send"}
                                </button>
                            </form>
                        </div>
                    </div>
                    <div class="artifact-panel" id="${this.ids.artifactPanel}">
                        <div class="artifact-resize-handle" id="${this.ids.artifactResize}"></div>
                        <div class="artifact-header">
                            <div class="artifact-title" id="${this.ids.artifactTitle}">Artifact</div>
                            <div class="artifact-tabs">
                                <button class="artifact-tab" id="${this.ids.artifactTabCode}" data-tab="code">Code</button>
                                <button class="artifact-tab" id="${this.ids.artifactTabPreview}" data-tab="preview">Preview</button>
                            </div>
                        </div>
                        <div class="artifact-body">
                            <pre class="artifact-code" id="${this.ids.artifactCode}"><code id="${this.ids.artifactCodeContent}"></code></pre>
                            <div class="artifact-preview" id="${this.ids.artifactPreview}">
                                <iframe id="${this.ids.artifactIframe}" sandbox="allow-scripts allow-same-origin"></iframe>
                            </div>
                        </div>
                        <div class="artifact-actions">
                            <button id="${this.ids.artifactCopy}" data-action="artifact-copy">Copy</button>
                            <button id="${this.ids.artifactDownload}" data-action="artifact-download">Download</button>
                            <button id="${this.ids.artifactClose}" data-action="artifact-close">Close</button>
                        </div>
                    </div>
                </div>
            `;
        }

        _cacheDom() {
            const byId = (id) => document.getElementById(id);
            this.els = {
                container: byId(this.ids.container),
                sidebar: byId(this.ids.sidebar),
                chatList: byId(this.ids.chatList),
                chatContainer: byId(this.ids.chatContainer),
                chatScroll: byId(this.ids.chatScroll),
                inputForm: byId(this.ids.inputForm),
                queryInput: byId(this.ids.queryInput),
                sendButton: byId(this.ids.sendButton),
                modelSelect: byId(this.ids.modelSelect),
                mainContent: byId(this.ids.mainContent),
                artifactPanel: byId(this.ids.artifactPanel),
                artifactResize: byId(this.ids.artifactResize),
                artifactTitle: byId(this.ids.artifactTitle),
                artifactCode: byId(this.ids.artifactCode),
                artifactCodeContent: byId(this.ids.artifactCodeContent),
                artifactPreview: byId(this.ids.artifactPreview),
                artifactIframe: byId(this.ids.artifactIframe),
                artifactCopy: byId(this.ids.artifactCopy),
                artifactDownload: byId(this.ids.artifactDownload),
                artifactClose: byId(this.ids.artifactClose),
                artifactTabCode: byId(this.ids.artifactTabCode),
                artifactTabPreview: byId(this.ids.artifactTabPreview),
                composerHelp: byId(this.ids.composerHelp),
                agentName: byId(this.ids.agentName),
                agentSubtitle: byId(this.ids.agentSubtitle),
                sidebarControls: byId(this.ids.sidebarControls),
            };
        }

        _bindEvents() {
            ensureMaterialIcons();
            const resizeHandler = (evt) => this._handleArtifactResize(evt);
            const stopResizeHandler = () => this._stopArtifactResize();

            const onInput = () => this._adjustTextareaHeight();
            const onKeydown = (event) => {
                if (event.key === "Enter" && !event.shiftKey) {
                    event.preventDefault();
                    this.handleSubmit();
                } else if (event.key === "Enter" && event.shiftKey) {
                    window.setTimeout(() => this._adjustTextareaHeight(), 0);
                }
            };
            const onFormSubmit = (event) => {
                event.preventDefault();
                this.handleSubmit();
            };

            const onChatContainerClick = (event) => {
                const artifactBtn = event.target.closest(".artifact-icon");
                if (artifactBtn) {
                    const artifactId = artifactBtn.getAttribute("data-artifact-id");
                    this.showArtifact(artifactId);
                    return;
                }
                const copyBtn = event.target.closest(".message-copy-btn");
                if (copyBtn) {
                    const messageId = copyBtn.getAttribute("data-message-id");
                    this.copyMessage(messageId);
                }
            };

            const onSidebarClick = (event) => {
                const btn = event.target.closest(".sidebar-btn");
                if (!btn) return;
                const action = btn.getAttribute("data-action");
                switch (action) {
                    case "new-chat":
                        this.startNewChat();
                        break;
                    case "clear-chats":
                        this.clearAllChats();
                        break;
                    case "toggle-dark":
                        this.toggleDarkMode();
                        break;
                    case "toggle-sidebar":
                        this.toggleSidebar();
                        break;
                    default:
                        break;
                }
            };

            const onChatListClick = (event) => {
                const deleteBtn = event.target.closest(".chat-delete-btn");
                if (deleteBtn) {
                    const chatId = deleteBtn.getAttribute("data-chat-id");
                    this.deleteChat(chatId);
                    event.stopPropagation();
                    return;
                }
                const item = event.target.closest(".rag-chat-item");
                if (!item) return;
                const chatId = item.getAttribute("data-chat-id");
                this.switchChat(chatId);
            };

            const onModelChange = (event) => {
                const chat = this._getActiveChat();
                if (chat) {
                    chat.model = event.target.value;
                    this.saveState();
                    this._renderChatList();
                }
            };

            const onArtifactCopy = (event) => {
                event.preventDefault();
                this.copyArtifactCode(event);
            };
            const onArtifactDownload = (event) => {
                event.preventDefault();
                this.downloadArtifact(event);
            };
            const onArtifactClose = (event) => {
                event.preventDefault();
                this.closeArtifactPanel();
            };
            const onArtifactTab = (event) => {
                event.preventDefault();
                const tab = event.currentTarget.getAttribute("data-tab");
                this.switchArtifactTab(tab);
            };

            const onResizeMouseDown = (event) => {
                this.isArtifactResizing = true;
                document.addEventListener("mousemove", resizeHandler);
                document.addEventListener("mouseup", stopResizeHandler);
                event.preventDefault();
            };

            this.els.queryInput.addEventListener("input", onInput);
            this.els.queryInput.addEventListener("keydown", onKeydown);
            this.els.inputForm.addEventListener("submit", onFormSubmit);
            this.els.chatContainer.addEventListener("click", onChatContainerClick);
            this.els.sidebarControls.addEventListener("click", onSidebarClick);
            this.els.chatList.addEventListener("click", onChatListClick);
            this.els.modelSelect.addEventListener("change", onModelChange);
            this.els.artifactCopy.addEventListener("click", onArtifactCopy);
            this.els.artifactDownload.addEventListener("click", onArtifactDownload);
            this.els.artifactClose.addEventListener("click", onArtifactClose);
            this.els.artifactTabCode.addEventListener("click", onArtifactTab);
            this.els.artifactTabPreview.addEventListener("click", onArtifactTab);
            this.els.artifactResize.addEventListener("mousedown", onResizeMouseDown);
            window.addEventListener("resize", onInput);

            EVENT_HANDLERS.set(this, {
                resizeHandler,
                stopResizeHandler,
                onInput,
                onKeydown,
                onFormSubmit,
                onChatContainerClick,
                onSidebarClick,
                onChatListClick,
                onModelChange,
                onArtifactCopy,
                onArtifactDownload,
                onArtifactClose,
                onArtifactTab,
                onResizeMouseDown,
            });
        }

        _initializeState() {
            this._loadState();
            this.loadModels().then(() => {
                this.initChats();
            }).catch(() => {
                this.initChats();
            });
            this._adjustTextareaHeight();
        }

        _loadState() {
            try {
                const storedChats = localStorage.getItem(this._storageKeys.chats);
                const storedActive = localStorage.getItem(this._storageKeys.activeChatId);
                if (storedChats) {
                    const parsed = JSON.parse(storedChats);
                    if (Array.isArray(parsed)) {
                        this.chats = parsed;
                    }
                }
                if (storedActive) {
                    this.activeChatId = storedActive;
                }
            } catch (error) {
                console.warn("[ChatWidget] Failed to load state", error);
            }
        }

        saveState() {
            try {
                localStorage.setItem(this._storageKeys.chats, JSON.stringify(this.chats));
                if (this.activeChatId) {
                    localStorage.setItem(this._storageKeys.activeChatId, this.activeChatId);
                }
                localStorage.setItem(this._storageKeys.artifactPanelWidth, this.artifactPanelWidth);
                localStorage.setItem(this._storageKeys.sidebarCollapsed, String(this.sidebarCollapsed));
                localStorage.setItem(this._storageKeys.isDarkMode, String(this.isDarkMode));
            } catch (error) {
                console.warn("[ChatWidget] Failed to save state", error);
            }
        }

        destroy() {
            const handlers = EVENT_HANDLERS.get(this);
            if (handlers) {
                this.els.queryInput.removeEventListener("input", handlers.onInput);
                this.els.queryInput.removeEventListener("keydown", handlers.onKeydown);
                this.els.inputForm.removeEventListener("submit", handlers.onFormSubmit);
                this.els.chatContainer.removeEventListener("click", handlers.onChatContainerClick);
                this.els.sidebarControls.removeEventListener("click", handlers.onSidebarClick);
                this.els.chatList.removeEventListener("click", handlers.onChatListClick);
                this.els.modelSelect.removeEventListener("change", handlers.onModelChange);
                this.els.artifactCopy.removeEventListener("click", handlers.onArtifactCopy);
                this.els.artifactDownload.removeEventListener("click", handlers.onArtifactDownload);
                this.els.artifactClose.removeEventListener("click", handlers.onArtifactClose);
                this.els.artifactTabCode.removeEventListener("click", handlers.onArtifactTab);
                this.els.artifactTabPreview.removeEventListener("click", handlers.onArtifactTab);
                this.els.artifactResize.removeEventListener("mousedown", handlers.onResizeMouseDown);
                document.removeEventListener("mousemove", handlers.resizeHandler);
                document.removeEventListener("mouseup", handlers.stopResizeHandler);
                window.removeEventListener("resize", handlers.onInput);
            }
            EVENT_HANDLERS.delete(this);
        }

        // -----------------------------------------------------------------
        // UI helpers
        // -----------------------------------------------------------------

        _adjustTextareaHeight() {
            const textarea = this.els.queryInput;
            const maxHeight = Math.floor(window.innerHeight * 0.33) || 260;
            const minHeight = 44;
            textarea.style.minHeight = `${minHeight}px`;
            textarea.style.maxHeight = `${maxHeight}px`;
            textarea.style.height = "auto";
            const scrollHeight = textarea.scrollHeight;
            const nextHeight = clamp(scrollHeight, minHeight, maxHeight);
            textarea.style.height = `${nextHeight}px`;
            textarea.style.overflowY = scrollHeight > maxHeight ? "auto" : "hidden";
        }

        _handleArtifactResize(event) {
            if (!this.isArtifactResizing) return;
            const windowWidth = window.innerWidth;
            const newWidth = windowWidth - event.clientX;
            const minWidth = 300;
            const maxWidth = windowWidth * 0.7;
            if (newWidth >= minWidth && newWidth <= maxWidth) {
                const widthPercent = (newWidth / windowWidth) * 100;
                this.els.artifactPanel.style.width = `${widthPercent}%`;
                this.els.mainContent.style.marginRight = `${widthPercent}%`;
                this.artifactPanelWidth = `${widthPercent}%`;
            }
        }

        _stopArtifactResize() {
            if (!this.isArtifactResizing) return;
            this.isArtifactResizing = false;
            this.saveState();
            const handlers = EVENT_HANDLERS.get(this);
            if (handlers) {
                document.removeEventListener("mousemove", handlers.resizeHandler);
                document.removeEventListener("mouseup", handlers.stopResizeHandler);
            }
        }

        _processStreamingText(text, context = {}) {
            const artifacts = [];
            let processedText = text;
            let hasCompleteArtifacts = false;

            const fingerprint = (value) => {
                const input = value || "";
                let hash = 0;
                for (let idx = 0; idx < input.length; idx += 1) {
                    hash = (hash << 5) - hash + input.charCodeAt(idx);
                    hash |= 0;
                }
                return Math.abs(hash).toString(36);
            };

            const messageSeed = context.messageId || "";

            const ensureUniqueId = (proposedId, content, title) => {
                const base = proposedId || `artifact-${fingerprint(`${messageSeed}::${title || ""}::${content || ""}`)}`;
                let id = base;
                let counter = 1;
                while (artifacts.some((item) => item.id === id)) {
                    id = `${base}-${counter++}`;
                }
                return id;
            };

            const normalizeParams = (input) => {
                const params = {};
                if (!input) return params;
                const clean = input.replace(/\|/g, " ");
                const regex = /(\w+)=(["'])([^"']*)\2|(\w+)=([^\s]+)/g;
                let pair;
                while ((pair = regex.exec(clean)) !== null) {
                    const key = (pair[1] || pair[4] || "").toLowerCase();
                    const value = pair[3] || pair[5] || "";
                    if (key) params[key] = value;
                }
                return params;
            };

            const languageToMime = (language) => {
                switch ((language || "").toLowerCase()) {
                    case "html":
                    case "htm":
                        return "text/html";
                    case "jsx":
                    case "tsx":
                        return "application/vnd.react";
                    case "mermaid":
                        return "application/vnd.mermaid";
                    case "svg":
                        return "image/svg+xml";
                    case "markdown":
                    case "md":
                        return "text/markdown";
                    case "json":
                        return "application/json";
                    default:
                        return null;
                }
            };

            const rendererToMime = (renderer) => {
                switch ((renderer || "").toLowerCase()) {
                    case "iframe":
                    case "html":
                        return "text/html";
                    case "mermaid":
                        return "application/vnd.mermaid";
                    default:
                        return null;
                }
            };

            const createArtifact = (params, content, fallbackTitle) => {
                const normalized = {};
                Object.keys(params || {}).forEach((key) => {
                    normalized[key.toLowerCase()] = params[key];
                });
                const languageMime = languageToMime(normalized.language);
                const rendererMime = rendererToMime(normalized.renderer);
                const artifact = {
                    id: ensureUniqueId(normalized.identifier, content, fallbackTitle),
                    type: normalized.type || languageMime || rendererMime || "text/plain",
                    title: normalized.title || fallbackTitle || "Untitled Artifact",
                    content: (content || "").trim(),
                };
                return artifact;
            };

            const injectIcon = (artifact) => {
                hasCompleteArtifacts = true;
                artifacts.push(artifact);
                return `<div class="artifact-icon" data-artifact-id="${artifact.id}"><span class="material-icons">code</span><span>${artifact.title}</span></div>`;
            };

            const newPattern = /:::artifact\{([^}]*)\}([\s\S]*?):::/gi;
            processedText = processedText.replace(newPattern, (full, paramsStr, content) => {
                const params = normalizeParams(paramsStr);
                const artifact = createArtifact(params, content, params.title);
                return injectIcon(artifact);
            });

            let legacyMatched = false;
            const legacyPattern = /:::Artifact\s+([^\n|]+)([^\n]*)\n([\s\S]*?):::Artifact\s*/gi;
            processedText = processedText.replace(legacyPattern, (full, titleSegment, paramSegment, content) => {
                legacyMatched = true;
                const params = normalizeParams(paramSegment);
                params.title = params.title || titleSegment.trim();
                const artifact = createArtifact(params, content, titleSegment.trim());
                return injectIcon(artifact);
            });

            const hasNewStart = /:::artifact\{[^}]*\}/i.test(text);
            const hasLegacyStart = /:::Artifact\s+[^\n]+/i.test(text);
            const hasIncompleteArtifact = (hasNewStart && !hasCompleteArtifacts) || (hasLegacyStart && !legacyMatched);

            if (hasIncompleteArtifact) {
                const artifactStart = Math.max(
                    text.search(/:::artifact\{/i),
                    text.search(/:::Artifact\s+[^\n]+/i),
                );
                if (artifactStart >= 0) {
                    processedText = text.substring(0, artifactStart);
                }
            }

            return { processedText, artifacts, hasCompleteArtifacts, hasIncompleteArtifact };
        }

        _formatTimestamp(value) {
            if (!value) {
                return "";
            }
            let date;
            if (value instanceof Date) {
                date = value;
            } else if (typeof value === "number") {
                date = new Date(value);
            } else if (typeof value === "string") {
                const parsed = new Date(value);
                if (!Number.isNaN(parsed.getTime())) {
                    date = parsed;
                } else {
                    return value;
                }
            }
            if (!date || Number.isNaN(date.getTime())) {
                return "";
            }
            return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
        }

        _renderMessageContent(message, element) {
            const contentEl = element.querySelector(".message-content");
            const nameEl = element.querySelector(".message-name");
            const timestampEl = element.querySelector(".message-timestamp");
            if (nameEl) {
                nameEl.textContent = message.role === "user" ? "You" : (message.name || (this.options.agent && this.options.agent.name) || "Assistant");
            }
            if (timestampEl) {
                const formatted = this._formatTimestamp(message.timestamp || (message.meta && message.meta.timestamp));
                if (formatted) {
                    timestampEl.textContent = formatted;
                    timestampEl.classList.remove("is-hidden");
                } else {
                    timestampEl.textContent = "";
                    timestampEl.classList.add("is-hidden");
                }
            }
            let displayText = message.content || "";
            let artifacts = [];
            if (message.role !== "user" && this.options.enableArtifacts && displayText) {
                if (displayText.includes("artifact-icon")) {
                    // Already processed HTML
                } else {
                    const result = this._processStreamingText(displayText, { messageId: message.id });
                    displayText = result.processedText;
                    artifacts = result.artifacts;
                    if (artifacts.length) {
                        const activeChat = this._getActiveChat();
                        if (activeChat) {
                            activeChat.artifacts = activeChat.artifacts || [];
                            artifacts.forEach((artifact) => {
                                const existing = activeChat.artifacts.find((item) => item.id === artifact.id);
                                if (!existing) {
                                    activeChat.artifacts.push(artifact);
                                    this._artifactMap.set(artifact.id, Object.assign({ chatId: activeChat.id, messageId: message.id }, artifact));
                                }
                            });
                        }
                    }
                }
            }
            if (displayText.includes("artifact-icon")) {
                contentEl.innerHTML = displayText;
            } else if (displayText.trim()) {
                const html = renderMarkdown(displayText);
                contentEl.innerHTML = html || displayText.replace(/\n/g, "<br>");
            } else {
                contentEl.innerHTML = "";
            }
        }

        _createMessageElement(message) {
            const wrapper = document.createElement("div");
            wrapper.className = `rag-message ${message.role === "user" ? "rag-user-message" : "rag-assistant-message"}`;
            wrapper.setAttribute("data-message-id", message.id);

            const header = document.createElement("div");
            header.className = "message-header";

            const nameSpan = document.createElement("span");
            nameSpan.className = "message-name";
            header.appendChild(nameSpan);

            const timestampSpan = document.createElement("span");
            timestampSpan.className = "message-timestamp is-hidden";
            header.appendChild(timestampSpan);

            const copyBtn = document.createElement("button");
            copyBtn.className = "message-copy-btn";
            copyBtn.innerHTML = '<span class="material-icons">content_copy</span>';
            copyBtn.setAttribute("aria-label", "Copy message");
            copyBtn.setAttribute("title", "Copy message");
            copyBtn.setAttribute("data-message-id", message.id);
            copyBtn.addEventListener("click", () => {
                navigator.clipboard.writeText(message.content || "");
                this.host.emit("copy", { id: message.id, message });
            });

            const bubble = document.createElement("div");
            bubble.className = "message-bubble";
            const content = document.createElement("div");
            content.className = "message-content";
            bubble.appendChild(content);

            const tray = document.createElement("div");
            tray.className = "message-tray";
            const trayCopyBtn = document.createElement("button");
            trayCopyBtn.className = "message-copy-btn";
            trayCopyBtn.innerHTML = '<span class="material-icons">content_copy</span>';
            trayCopyBtn.setAttribute("data-message-id", message.id);
            trayCopyBtn.addEventListener("click", () => {
                navigator.clipboard.writeText(message.content || "");
                this.host.emit("copy", { id: message.id, message });
            });
            tray.appendChild(trayCopyBtn);

            wrapper.appendChild(header);
            wrapper.appendChild(bubble);
            wrapper.appendChild(tray);
            return wrapper;
        }

        _renderMessages() {
            this.els.chatContainer.innerHTML = "";
            this._messageMap.clear();
            const activeChat = this._getActiveChat();
            if (!activeChat) return;
            activeChat.messages.forEach((message) => {
                const element = this._createMessageElement(message);
                this._renderMessageContent(message, element);
                this.els.chatContainer.appendChild(element);
                this._messageMap.set(message.id, { chatId: activeChat.id, element, message });
            });
            this._scrollToBottom();
        }

        _renderChatList() {
            this.els.chatList.innerHTML = "";
            const isCollapsed = this.sidebarCollapsed;
            this.chats.forEach((chat) => {
                const item = document.createElement("div");
                item.className = `rag-chat-item${chat.id === this.activeChatId ? " active" : ""}`;
                item.setAttribute("data-chat-id", chat.id);
                if (chat.title) {
                    item.setAttribute("title", chat.title);
                }
                const badgeValue = Number(chat.badge ?? (chat.meta && chat.meta.badge));
                const hasBadge = !Number.isNaN(badgeValue) && badgeValue > 0;
                const badgeLabel = hasBadge ? (badgeValue > 999 ? "999+" : String(badgeValue)) : null;
                if (isCollapsed) {
                    const initial = (chat.title || "Chat").charAt(0).toUpperCase();
                    item.setAttribute("data-initial", initial);
                    const tooltip = document.createElement("div");
                    tooltip.className = "tooltip";
                    tooltip.textContent = chat.title;
                    item.appendChild(tooltip);
                    if (hasBadge) {
                        const badge = document.createElement("span");
                        badge.className = "chat-badge";
                        badge.textContent = badgeLabel;
                        item.appendChild(badge);
                    }
                } else {
                    const titleSpan = document.createElement("span");
                    titleSpan.className = "chat-title";
                    titleSpan.textContent = chat.title;
                    const modelSpan = document.createElement("span");
                    modelSpan.className = "chat-model";
                    modelSpan.textContent = chat.model || "default";
                    const deleteBtn = document.createElement("button");
                    deleteBtn.className = "chat-delete-btn";
                    deleteBtn.setAttribute("data-chat-id", chat.id);
                    deleteBtn.innerHTML = '<span class="material-icons">close</span>';
                    deleteBtn.setAttribute("aria-label", "Delete chat");
                    item.appendChild(titleSpan);
                    if (hasBadge) {
                        const badge = document.createElement("span");
                        badge.className = "chat-badge";
                        badge.textContent = badgeLabel;
                        item.appendChild(badge);
                    }
                    item.appendChild(modelSpan);
                    item.appendChild(deleteBtn);
                }
                this.els.chatList.appendChild(item);
            });
        }

        _scrollToBottom() {
            window.requestAnimationFrame(() => {
                this.els.chatScroll.scrollTop = this.els.chatScroll.scrollHeight;
            });
        }

        _getActiveChat() {
            return this.chats.find((chat) => chat.id === this.activeChatId) || null;
        }

        _ensureActiveChat() {
            if (!this.activeChatId || !this._getActiveChat()) {
                if (!this.chats.length) {
                    this.startNewChat();
                } else {
                    this.activeChatId = this.chats[0].id;
                }
            }
            return this._getActiveChat();
        }

        _generateChatTitle() {
            return `Chat ${formatTime(new Date())}`;
        }

        // -----------------------------------------------------------------
        // Public-ish API consumed by ChatWidget
        // -----------------------------------------------------------------

        setMessages(messages) {
            const chat = this._ensureActiveChat();
            if (!chat) return;
            chat.messages = Array.from(messages || []).map((msg) => this._normalizeMessage(msg));
            chat.artifacts = [];
            chat.messages.forEach((msg) => {
                if (msg.content && msg.role !== "user") {
                    const parsed = this._processStreamingText(msg.content, { messageId: msg.id });
                    parsed.artifacts.forEach((artifact) => {
                        this._artifactMap.set(artifact.id, Object.assign({ chatId: chat.id, messageId: msg.id }, artifact));
                        chat.artifacts.push(artifact);
                    });
                }
            });
            this._renderMessages();
            this.saveState();
        }

        addMessage(message) {
            const chat = this._ensureActiveChat();
            if (!chat) return null;
            const normalized = this._normalizeMessage(message);
            chat.messages.push(normalized);
            chat.model = this.els.modelSelect.value || chat.model || "default";
            const element = this._createMessageElement(normalized);
            this._renderMessageContent(normalized, element);
            this.els.chatContainer.appendChild(element);
            this._messageMap.set(normalized.id, { chatId: chat.id, element, message: normalized });
            this._scrollToBottom();
            this.saveState();
            return normalized.id;
        }

        updateMessage(messageId, updates) {
            const record = this._messageMap.get(messageId);
            if (!record) return;
            Object.assign(record.message, updates || {});
            this._renderMessageContent(record.message, record.element);
            this.saveState();
        }

        removeMessage(messageId) {
            const record = this._messageMap.get(messageId);
            if (!record) return;
            const chat = this.chats.find((item) => item.id === record.chatId);
            if (chat) {
                chat.messages = chat.messages.filter((msg) => msg.id !== messageId);
            }
            record.element.remove();
            this._messageMap.delete(messageId);
            this.saveState();
        }

        clearMessages() {
            const chat = this._getActiveChat();
            if (!chat) return;
            chat.messages = [];
            chat.artifacts = [];
            this._messageMap.clear();
            this._artifactMap.clear();
            this.els.chatContainer.innerHTML = "";
            this.closeArtifactPanel();
            this.saveState();
        }

        startStream(message) {
            const payload = this._normalizeMessage(Object.assign({}, message, { streaming: true }));
            const chat = this._ensureActiveChat();
            if (!chat) return payload.id;
            chat.messages.push(payload);
            chat.model = this.els.modelSelect.value || chat.model || "default";
            const element = this._createMessageElement(payload);
            element.classList.add("streaming");
            this.els.chatContainer.appendChild(element);
            this._messageMap.set(payload.id, { chatId: chat.id, element, message: payload });
            this._scrollToBottom();
            this.saveState();
            return payload.id;
        }

        appendStream(messageId, chunk) {
            const record = this._messageMap.get(messageId);
            if (!record) return;
            record.message.content = (record.message.content || "") + (chunk || "");
            record.message.streaming = true;
            this._renderMessageContent(record.message, record.element);
            this._scrollToBottom();
            this._handleArtifactStreaming(record.message);
            this.saveState();
        }

        finishStream(messageId, finalChunk) {
            const record = this._messageMap.get(messageId);
            if (!record) return;
            if (finalChunk) {
                record.message.content = (record.message.content || "") + finalChunk;
            }
            record.message.streaming = false;
            record.element.classList.remove("streaming");
            const finishedAt = new Date().toISOString();
            record.message.timestamp = finishedAt;
            record.message.meta = record.message.meta || {};
            record.message.meta.timestamp = finishedAt;
            this._renderMessageContent(record.message, record.element);
            this._handleArtifactStreaming(record.message, true);
            this.saveState();
        }

        setAgent(agent) {
            this.options.agent = Object.assign({}, this.options.agent || {}, agent || {});
            this.els.agentName.textContent = this.options.agent.name || "Assistant";
            this.els.agentSubtitle.textContent = this.options.agent.subtitle || "";
        }

        setTheme(theme) {
            if (theme === "dark") {
                this.isDarkMode = true;
            } else if (theme === "light") {
                this.isDarkMode = false;
            } else if (theme === "auto") {
                // leave as-is
            }
            if (this.isDarkMode) {
                this.els.container.classList.add("rag-dark");
            } else {
                this.els.container.classList.remove("rag-dark");
            }
            this.saveState();
        }

        focusComposer() {
            this.els.queryInput.focus();
        }

        // -----------------------------------------------------------------
        // Chat operations
        // -----------------------------------------------------------------

        _normalizeMessage(message) {
            const id = message.id || createUniqueId("message");
            const meta = Object.assign({}, message.meta || {});
            const existingTimestamp = message.timestamp || meta.timestamp;
            const resolvedTimestamp = existingTimestamp || new Date().toISOString();
            if (!meta.timestamp) {
                meta.timestamp = resolvedTimestamp;
            }
            return {
                id,
                role: message.role || "assistant",
                content: message.content || "",
                name: message.name,
                timestamp: resolvedTimestamp,
                streaming: Boolean(message.streaming),
                meta,
            };
        }

        initChats() {
            if (!this.chats.length) {
                const chatId = createUniqueId("chat");
                const initialMessages = Array.isArray(this.options.messages) ? this.options.messages.map((msg) => this._normalizeMessage(msg)) : [];
                this.chats.push({
                    id: chatId,
                    title: this._generateChatTitle(),
                    messages: initialMessages,
                    artifacts: [],
                    model: (this.availableModels[0]) || "default",
                });
                this.activeChatId = chatId;
            } else if (!this.activeChatId || !this.chats.find((chat) => chat.id === this.activeChatId)) {
                this.activeChatId = this.chats[0].id;
            }
            this.chats.forEach((chat) => {
                chat.messages = (chat.messages || []).map((msg) => this._normalizeMessage(msg));
                chat.artifacts = chat.artifacts || [];
                if (chat.badge && Number(chat.badge) > 0) {
                    chat.badge = Math.min(999, Math.max(1, Number(chat.badge)));
                } else {
                    delete chat.badge;
                }
                chat.messages.forEach((msg) => {
                    if (msg.content && msg.role !== "user") {
                        const parsed = this._processStreamingText(msg.content, { messageId: msg.id });
                        parsed.artifacts.forEach((artifact) => {
                            this._artifactMap.set(artifact.id, Object.assign({ chatId: chat.id, messageId: msg.id }, artifact));
                            if (!chat.artifacts.some((item) => item.id === artifact.id)) {
                                chat.artifacts.push(artifact);
                            }
                        });
                    }
                });
            });
            this._renderChatList();
            this._renderMessages();
            this.switchToModelForActiveChat();
            const toggleBtn = this.els.sidebarControls.querySelector("[data-action='toggle-sidebar'] .material-icons");
            const toggleTooltip = this.els.sidebarControls.querySelector("[data-action='toggle-sidebar'] .tooltip");
            if (this.sidebarCollapsed) {
                this.els.sidebar.classList.add("collapsed");
                if (toggleBtn) {
                    toggleBtn.textContent = "menu";
                }
                if (toggleTooltip) {
                    toggleTooltip.textContent = "Expand";
                }
            } else {
                this.els.sidebar.classList.remove("collapsed");
                if (toggleBtn) {
                    toggleBtn.textContent = "menu_open";
                }
                if (toggleTooltip) {
                    toggleTooltip.textContent = "Collapse";
                }
            }
            this.els.container.classList.toggle("sidebar-collapsed", this.sidebarCollapsed);
            if (this.isDarkMode) {
                this.els.container.classList.add("rag-dark");
            }
        }

        startNewChat() {
            const newId = createUniqueId("chat");
            const newChat = {
                id: newId,
                title: this._generateChatTitle(),
                messages: [],
                artifacts: [],
                model: this.els.modelSelect.value || "default",
            };
            this.chats.push(newChat);
            this.activeChatId = newId;
            this._renderChatList();
            this._renderMessages();
            this.saveState();
        }

        switchChat(chatId) {
            if (!chatId || chatId === this.activeChatId) return;
            const exists = this.chats.some((chat) => chat.id === chatId);
            if (!exists) return;
            this.activeChatId = chatId;
            const current = this.chats.find((chat) => chat.id === chatId);
            if (current && current.badge) {
                delete current.badge;
            }
            this.closeArtifactPanel();
            this._renderChatList();
            this._renderMessages();
            this.switchToModelForActiveChat();
            this.saveState();
        }

        deleteChat(chatId) {
            const idx = this.chats.findIndex((chat) => chat.id === chatId);
            if (idx === -1) return;
            const confirmed = typeof window !== "undefined" ? window.confirm("Delete this chat?") : true;
            if (!confirmed) return;
            const removed = this.chats.splice(idx, 1);
            if (removed.length && removed[0].id === this.activeChatId) {
                this.closeArtifactPanel();
                if (this.chats.length) {
                    this.activeChatId = this.chats[0].id;
                } else {
                    this.startNewChat();
                    return;
                }
            }
            this._renderChatList();
            this._renderMessages();
            this.saveState();
        }

        clearAllChats() {
            const confirmed = typeof window !== "undefined" ? window.confirm("Delete all chats?") : true;
            if (!confirmed) return;
            this.chats = [];
            this.activeChatId = null;
            this.closeArtifactPanel();
            this.saveState();
            this.startNewChat();
        }

        toggleSidebar() {
            this.sidebarCollapsed = !this.sidebarCollapsed;
            this.els.sidebar.classList.toggle("collapsed", this.sidebarCollapsed);
            const toggleBtn = this.els.sidebarControls.querySelector("[data-action='toggle-sidebar'] .material-icons");
            const tooltip = this.els.sidebarControls.querySelector("[data-action='toggle-sidebar'] .tooltip");
            if (toggleBtn) {
                toggleBtn.textContent = this.sidebarCollapsed ? "menu" : "menu_open";
            }
            if (tooltip) {
                tooltip.textContent = this.sidebarCollapsed ? "Expand" : "Collapse";
            }
            this.els.container.classList.toggle("sidebar-collapsed", this.sidebarCollapsed);
            this._renderChatList();
            this.saveState();
        }

        setChatBadge(chatId, value) {
            const chat = this.chats.find((item) => item.id === chatId);
            if (!chat) return;
            const numeric = Number(value);
            if (Number.isNaN(numeric) || numeric <= 0) {
                delete chat.badge;
            } else {
                chat.badge = Math.min(999, Math.max(0, Math.floor(numeric)));
            }
            this._renderChatList();
            this.saveState();
        }

        toggleDarkMode() {
            this.isDarkMode = !this.isDarkMode;
            this.els.container.classList.toggle("rag-dark", this.isDarkMode);
            this.saveState();
        }

        switchToModelForActiveChat() {
            const chat = this._getActiveChat();
            if (!chat) return;
            if (chat.model && Array.from(this.els.modelSelect.options).some((opt) => opt.value === chat.model)) {
                this.els.modelSelect.value = chat.model;
            }
        }

        getChatSummaries() {
            return this.chats.map((chat) => ({
                id: chat.id,
                title: chat.title,
                badge: chat.badge || 0,
                model: chat.model || "default",
                isActive: chat.id === this.activeChatId,
            }));
        }

        updateChatTitle(query) {
            const chat = this._getActiveChat();
            if (!chat) return;
            if (chat.messages.length <= 1) {
                chat.title = query.substring(0, 30) + (query.length > 30 ? "..." : "");
                this._renderChatList();
                this.saveState();
            }
        }

        handleSubmit() {
            const query = this.els.queryInput.value.trim();
            if (!query) return;
            const activeChat = this._ensureActiveChat();
            if (!activeChat) return;

            const userMessage = {
                role: "user",
                content: query,
            };

            if (this.options.autoAppendUserMessages) {
                this.addMessage(userMessage);
            }
            this.updateChatTitle(query);
            activeChat.model = this.els.modelSelect.value || activeChat.model || "default";
            if (activeChat.badge) {
                delete activeChat.badge;
                this._renderChatList();
            }
            this.els.queryInput.value = "";
            this._adjustTextareaHeight();

            const sendResult = this.host.emit("send", {
                id: createUniqueId("prompt"),
                text: query,
                message: userMessage,
            });

            if (!sendResult || !sendResult.handled) {
                this.streamMockResponse();
            }
            this.saveState();
        }

        async loadModels() {
            try {
                const response = await fetch("/v1/models");
                if (!response.ok) throw new Error(`HTTP ${response.status}`);
                const data = await response.json();
                const models = Array.isArray(data?.data) ? data.data : [];
                this.availableModels = models.map((model) => model.id);
                this.els.modelSelect.innerHTML = "";
                if (!models.length) {
                    const option = document.createElement("option");
                    option.textContent = "No models";
                    this.els.modelSelect.appendChild(option);
                } else {
                    models.forEach((model) => {
                        const option = document.createElement("option");
                        option.value = model.id;
                        option.textContent = model.id;
                        this.els.modelSelect.appendChild(option);
                    });
                }
                if (models.length) {
                    this.els.modelSelect.value = models[0].id;
                }
            } catch (error) {
                console.warn("[ChatWidget] Failed to load models", error);
                this.availableModels = [];
                this.els.modelSelect.innerHTML = "<option>Default</option>";
            }
        }

        // -----------------------------------------------------------------
        // Artifact handling
        // -----------------------------------------------------------------

        showArtifact(artifactId) {
            const artifact = this._artifactMap.get(artifactId);
            if (!artifact) return;
            this.currentArtifact = artifact;
            this.els.artifactTitle.textContent = artifact.title;
            this.els.artifactCodeContent.textContent = artifact.content;
            const lang = this.getLanguageFromType(artifact.type);
            this.els.artifactCodeContent.className = `language-${lang}`;
            if (typeof Prism !== "undefined") {
                try {
                    Prism.highlightElement(this.els.artifactCodeContent);
                } catch (error) {
                    console.warn("[ChatWidget] Prism highlight failed", error);
                }
            }
            this.els.artifactPanel.classList.add("visible");
            this.els.mainContent.classList.add("with-artifact");
            if (this.artifactPanelWidth) {
                this.els.artifactPanel.style.width = this.artifactPanelWidth;
                this.els.mainContent.style.marginRight = this.artifactPanelWidth;
            }
            this.switchArtifactTab("preview");
            this.updateArtifactPreview();
        }

        updateArtifactPreview() {
            if (!this.currentArtifact) return;
            const iframe = this.els.artifactIframe;
            if (this.currentArtifact.type === "text/html") {
                const blob = new Blob([this.currentArtifact.content], { type: "text/html" });
                iframe.src = URL.createObjectURL(blob);
            } else if (this.currentArtifact.type === "image/svg+xml") {
                const svgContent = `<!DOCTYPE html><html><body style="margin:0;padding:20px;display:flex;justify-content:center;align-items:center;min-height:100vh;">${this.currentArtifact.content}</body></html>`;
                const blob = new Blob([svgContent], { type: "text/html" });
                iframe.src = URL.createObjectURL(blob);
            } else {
                iframe.src = "data:text/html,<body style='padding:20px;font-family:monospace;'>Preview not available for this file type</body>";
            }
        }

        switchArtifactTab(tab) {
            const codeTab = this.els.artifactTabCode;
            const previewTab = this.els.artifactTabPreview;
            const codePanel = this.els.artifactCode;
            const previewPanel = this.els.artifactPreview;
            codeTab.classList.remove("active");
            previewTab.classList.remove("active");
            codePanel.classList.remove("visible");
            previewPanel.classList.remove("visible");
            if (tab === "code") {
                codeTab.classList.add("active");
                codePanel.classList.add("visible");
            } else {
                previewTab.classList.add("active");
                previewPanel.classList.add("visible");
            }
        }

        copyArtifactCode(event) {
            if (!this.currentArtifact) return;
            navigator.clipboard.writeText(this.currentArtifact.content).then(() => {
                const btn = event.currentTarget;
                const original = btn.textContent;
                btn.textContent = "Copied!";
                setTimeout(() => { btn.textContent = original; }, 2000);
            });
            this.host.emit("artifact:save", { artifactId: this.currentArtifact.id, artifact: this.currentArtifact, action: "copy" });
        }

        downloadArtifact() {
            if (!this.currentArtifact) return;
            const extension = this.getFileExtension(this.currentArtifact.type);
            const filename = `${this.currentArtifact.id}.${extension}`;
            const blob = new Blob([this.currentArtifact.content], { type: this.currentArtifact.type });
            const url = URL.createObjectURL(blob);
            const anchor = document.createElement("a");
            anchor.href = url;
            anchor.download = filename;
            document.body.appendChild(anchor);
            anchor.click();
            document.body.removeChild(anchor);
            URL.revokeObjectURL(url);
            this.host.emit("artifact:save", { artifactId: this.currentArtifact.id, artifact: this.currentArtifact, action: "download" });
        }

        getFileExtension(type) {
            const map = {
                "text/html": "html",
                "application/vnd.react": "jsx",
                "application/vnd.mermaid": "mmd",
                "image/svg+xml": "svg",
            };
            return map[type] || "txt";
        }

        getLanguageFromType(type) {
            const typeMap = {
                "text/html": "html",
                "application/vnd.react": "jsx",
                "application/vnd.mermaid": "mermaid",
                "image/svg+xml": "svg",
            };
            return typeMap[type] || "markup";
        }

        closeArtifactPanel() {
            this.els.artifactPanel.classList.remove("visible");
            this.els.artifactPanel.style.width = "";
            this.els.mainContent.classList.remove("with-artifact");
            this.els.mainContent.style.width = "";
            this.els.mainContent.style.marginRight = "";
            if (this.els.artifactIframe) {
                this.els.artifactIframe.src = "about:blank";
            }
            if (this.els.artifactCodeContent) {
                this.els.artifactCodeContent.textContent = "";
                this.els.artifactCodeContent.className = "";
            }
            this.currentArtifact = null;
            this.streamContext = {
                target: null,
                messageId: null,
                buffer: "",
                isRedirecting: false,
                completedMessageId: null,
                messageArtifactCount: 0,
            };
        }

        _handleArtifactStreaming(message, isFinal = false) {
            if (!this.options.enableArtifacts) return;
            if (!message || message.role === "user") return;
            const text = message.content || "";
            if (!text) return;
            if (!this.streamContext.isRedirecting && this.streamContext.completedMessageId === message.id) {
                const previousBuffer = this.streamContext.buffer || "";
                const startIndex = previousBuffer.length > text.length ? text.length : previousBuffer.length;
                const newSegment = text.substring(startIndex);
                if (!/:::artifact\{|:::Artifact\s+[^\n]+/i.test(newSegment)) {
                    return;
                }
            }
            if (!this.streamContext.isRedirecting && this.shouldRedirectToArtifact(text)) {
                this.streamContext.isRedirecting = true;
                this.streamContext.messageId = message.id;
                this.streamContext.buffer = text;
                this.streamContext.target = this.els.artifactCodeContent;
                this.streamContext.completedMessageId = null;
                this.streamContext.messageArtifactCount = 0;
                this.els.artifactPanel.classList.add("visible");
                this.els.mainContent.classList.add("with-artifact");
                if (this.artifactPanelWidth) {
                    this.els.artifactPanel.style.width = this.artifactPanelWidth;
                    this.els.mainContent.style.marginRight = this.artifactPanelWidth;
                }
                this.els.artifactTitle.textContent = "Streaming Artifact...";
                this.switchArtifactTab("code");
            }
            if (this.streamContext.isRedirecting) {
                this.streamContext.buffer = text;
                const lower = text.toLowerCase();
                const modernMatch = /:::artifact\{[^}]*\}/i.exec(text);
                const legacyMatch = /:::Artifact\s+([^\n]+)([^\n]*)\n/i.exec(text);
                if (modernMatch) {
                    const headerEnd = modernMatch.index + modernMatch[0].length;
                    const closeIndex = lower.indexOf(":::", headerEnd);
                    if (closeIndex !== -1) {
                        const artifactContent = text.substring(headerEnd, closeIndex);
                        this.streamContext.target.textContent = artifactContent;
                        this._finalizeStreamingArtifact(message, text);
                    } else {
                        const partial = text.substring(headerEnd);
                        this.streamContext.target.textContent = partial;
                    }
                } else if (legacyMatch) {
                    const headerEnd = legacyMatch.index + legacyMatch[0].length;
                    const closeIndex = lower.indexOf(":::artifact", headerEnd);
                    if (closeIndex !== -1) {
                        const artifactContent = text.substring(headerEnd, closeIndex);
                        this.streamContext.target.textContent = artifactContent;
                        this._finalizeStreamingArtifact(message, text);
                    } else {
                        const partial = text.substring(headerEnd);
                        this.streamContext.target.textContent = partial;
                    }
                }
            }
            if (isFinal) {
                this.streamContext.isRedirecting = false;
                this.streamContext.target = null;
            }
        }

        _finalizeStreamingArtifact(message, text) {
            const parsed = this._processStreamingText(text, { messageId: message.id });
            if (!parsed.artifacts.length) {
                return;
            }
            const chat = this._getActiveChat();
            parsed.artifacts.forEach((artifact) => {
                if (chat) {
                    chat.artifacts = chat.artifacts || [];
                    const exists = chat.artifacts.some((item) => item.id === artifact.id);
                    if (!exists) {
                        chat.artifacts.push(artifact);
                    }
                }
                this._artifactMap.set(artifact.id, Object.assign({ chatId: chat ? chat.id : null, messageId: message.id }, artifact));
            });

            const artifact = parsed.artifacts[parsed.artifacts.length - 1];
            this.currentArtifact = Object.assign({}, artifact, { messageId: message.id });
            this.els.artifactTitle.textContent = artifact.title;
            this.els.artifactCodeContent.textContent = artifact.content;
            const lang = this.getLanguageFromType(artifact.type);
            this.els.artifactCodeContent.className = `language-${lang}`;
            if (typeof Prism !== "undefined") {
                try {
                    Prism.highlightElement(this.els.artifactCodeContent);
                } catch (error) {
                    console.warn("[ChatWidget] Prism highlight failed", error);
                }
            }
            this.switchArtifactTab("preview");
            this.updateArtifactPreview();
            this.streamContext.completedMessageId = message.id;
            this.streamContext.buffer = text;
            this.streamContext.messageArtifactCount = parsed.artifacts.length;
            this.streamContext.messageId = message.id;
            this.streamContext.isRedirecting = false;
            this.streamContext.target = null;
        }

        shouldRedirectToArtifact(text) {
            if (this.streamContext.isRedirecting) {
                return false;
            }
            if (!text) return false;
            const hasModernMarker = /:::artifact\{/i.test(text);
            const hasLegacyMarker = /:::Artifact\s+[^\n]+/i.test(text);
            return hasModernMarker || hasLegacyMarker;
        }

        copyMessage(messageId) {
            const record = this._messageMap.get(messageId);
            if (!record) return;
            navigator.clipboard.writeText(record.message.content || "");
            this.host.emit("copy", { id: messageId, message: record.message });
        }

        // -----------------------------------------------------------------
        // Demo streaming helpers
        // -----------------------------------------------------------------

        streamMockResponse() {
            const assistantId = this.startStream({ role: "assistant", content: "", streaming: true });
            const response = this.options.demoResponse || DEFAULT_DEMO_RESPONSE;
            let index = 0;
            const step = () => {
                if (index >= response.length) {
                    this.finishStream(assistantId);
                    return;
                }
                const chunk = response.slice(index, index + 8);
                index += 8;
                this.appendStream(assistantId, chunk);
                window.setTimeout(step, 20);
            };
            step();
        }
    }

    class ChatWidget extends EventBus {
        constructor(target, options = {}) {
            super();
            this.options = options || {};
            injectStyles();
            this._initializeLayout(target);
            this._renderHost();

            this._pendingSetMessages = null;
            this._pendingAdds = [];
            this._pendingUpdates = [];
            this._pendingRemovals = [];
            this._pendingClear = false;
            this._pendingStartStreams = [];
            this._pendingAppendStreams = [];
            this._pendingFinishStreams = [];
            this._pendingTheme = null;
            this._pendingAgent = null;
            this._pendingFocus = false;
            this._pendingBadgeUpdates = [];

            this._readyPromise = this._ensureDomReady().then(() => {
                const mount = document.getElementById(this._mountDomId);
                if (!mount) throw new Error("ChatWidget mount element missing");
                this._app = new TenzinChatApp(mount, this.options, this);
                this._flushPendingOperations();
            });
        }

        _initializeLayout(target) {
            const config = {
                css: "chat-widget-host",
                type: "none",
                rows: [{ id: createUniqueId("chat-host"), gravity: 1 }],
            };
            this._layoutCellId = config.rows[0].id;
            if (target && typeof target.attach === "function") {
                this._layout = new dhx.Layout(null, config);
                target.attach(this._layout);
            } else if (target && typeof target.attachHTML === "function") {
                target.attachHTML(`<div class="chat-widget-host" id="${config.rows[0].id}-container"></div>`);
                const dom = document.getElementById(`${config.rows[0].id}-container`);
                this._layout = new dhx.Layout(dom, config);
            } else if (typeof target === "string") {
                const dom = document.querySelector(target);
                if (!dom) throw new Error("Unable to mount ChatWidget – target not found.");
                this._layout = new dhx.Layout(dom, config);
            } else if (target && target.nodeType === 1) {
                this._layout = new dhx.Layout(target, config);
            } else {
                throw new Error("Unable to mount ChatWidget – target not found.");
            }
        }

        _renderHost() {
            const cell = this._layout.getCell(this._layoutCellId);
            if (!cell) throw new Error("ChatWidget layout cell not available");
            this._mountDomId = createUniqueId("chat-root");
            cell.attachHTML(`<div id="${this._mountDomId}" style="width:100%;height:100%;"></div>`);
        }

        _ensureDomReady() {
            return new Promise((resolve) => {
                const check = () => {
                    if (document.getElementById(this._mountDomId)) {
                        resolve();
                        return;
                    }
                    requestAnimationFrame(check);
                };
                check();
            });
        }

        _flushPendingOperations() {
            if (!this._app) return;
            if (this._pendingSetMessages) {
                this._app.setMessages(this._pendingSetMessages);
                this._pendingSetMessages = null;
            }
            if (this._pendingClear) {
                this._app.clearMessages();
                this._pendingClear = false;
            }
            this._pendingAdds.forEach((message) => this._app.addMessage(message));
            this._pendingAdds.length = 0;
            this._pendingUpdates.forEach(({ id, updates }) => this._app.updateMessage(id, updates));
            this._pendingUpdates.length = 0;
            this._pendingRemovals.forEach((id) => this._app.removeMessage(id));
            this._pendingRemovals.length = 0;
            this._pendingStartStreams.forEach((message) => this._app.startStream(message));
            this._pendingStartStreams.length = 0;
            this._pendingAppendStreams.forEach(({ id, chunk }) => this._app.appendStream(id, chunk));
            this._pendingAppendStreams.length = 0;
            this._pendingFinishStreams.forEach(({ id, chunk }) => this._app.finishStream(id, chunk));
            this._pendingFinishStreams.length = 0;
            if (this._pendingAgent) {
                this._app.setAgent(this._pendingAgent);
                this._pendingAgent = null;
            }
            if (this._pendingTheme) {
                this._app.setTheme(this._pendingTheme);
                this._pendingTheme = null;
            }
            if (this._pendingFocus) {
                this._app.focusComposer();
                this._pendingFocus = false;
            }
            if (this._pendingBadgeUpdates.length) {
                this._pendingBadgeUpdates.forEach(({ id, badge }) => this._app.setChatBadge(id, badge));
                this._pendingBadgeUpdates.length = 0;
            }
        }

        setMessages(messages) {
            if (this._app) {
                this._app.setMessages(messages);
            } else {
                this._pendingSetMessages = Array.isArray(messages) ? messages.map((msg) => Object.assign({}, msg)) : [];
            }
        }

        addMessage(message) {
            if (!message) return null;
            if (this._app) {
                return this._app.addMessage(message);
            }
            const clone = Object.assign({}, message);
            if (!clone.id) {
                clone.id = createUniqueId("message");
            }
            this._pendingAdds.push(clone);
            return clone.id;
        }

        updateMessage(messageId, updates) {
            if (this._app) {
                this._app.updateMessage(messageId, updates);
            } else {
                this._pendingUpdates.push({ id: messageId, updates: Object.assign({}, updates) });
            }
        }

        removeMessage(messageId) {
            if (this._app) {
                this._app.removeMessage(messageId);
            } else {
                this._pendingRemovals.push(messageId);
            }
        }

        clearMessages() {
            if (this._app) {
                this._app.clearMessages();
            } else {
                this._pendingClear = true;
                this._pendingAdds.length = 0;
                this._pendingUpdates.length = 0;
                this._pendingRemovals.length = 0;
                this._pendingStartStreams.length = 0;
                this._pendingAppendStreams.length = 0;
                this._pendingFinishStreams.length = 0;
            }
        }

        startStream(message) {
            if (!message) return null;
            if (this._app) {
                return this._app.startStream(message);
            }
            const clone = Object.assign({}, message);
            if (!clone.id) {
                clone.id = createUniqueId("message");
            }
            if (!clone.streaming) {
                clone.streaming = true;
            }
            this._pendingStartStreams.push(clone);
            return clone.id;
        }

        appendStream(messageId, chunk) {
            if (this._app) {
                this._app.appendStream(messageId, chunk);
            } else {
                this._pendingAppendStreams.push({ id: messageId, chunk });
            }
        }

        finishStream(messageId, finalChunk) {
            if (this._app) {
                this._app.finishStream(messageId, finalChunk);
            } else {
                this._pendingFinishStreams.push({ id: messageId, chunk: finalChunk });
            }
        }

        setAgent(agent) {
            if (this._app) {
                this._app.setAgent(agent);
            } else {
                this._pendingAgent = Object.assign({}, agent);
            }
        }

        setTheme(theme) {
            if (this._app) {
                this._app.setTheme(theme);
            } else {
                this._pendingTheme = theme;
            }
        }

        focusComposer() {
            if (this._app) {
                this._app.focusComposer();
            } else {
                this._pendingFocus = true;
            }
        }

        setChatBadge(chatId, badge) {
            if (!chatId) return;
            if (this._app) {
                this._app.setChatBadge(chatId, badge);
            } else {
                this._pendingBadgeUpdates.push({ id: chatId, badge });
            }
        }

        getChats() {
            if (!this._app) {
                return [];
            }
            return this._app.getChatSummaries();
        }

        destroy() {
            if (this._app) {
                this._app.destroy();
            }
            if (this._layout) {
                this._layout.destructor();
            }
        }
    }

    globalThis.customdhx = globalThis.customdhx || {};
    globalThis.customdhx.ChatWidget = ChatWidget;
}());
