// Chat Main - AI Chat frontend with multi-session support
// Sessions are persisted server-side via AiChatSession / AiChatMessage

let sessions = [];
let currentSessionId = null;
let chatMessages = [];
let isWaiting = false;
let showingArchived = false;
let selectedDocIds = new Set();  // IDs of context docs attached to current chat (int or string)

function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

// ---- Sidebar ----

async function loadSessions() {
    const archived = showingArchived ? 1 : 0;
    sessions = await AiChatSession.list(archived);
    renderSidebar();
}

function renderSidebar() {
    const container = document.getElementById('sessionList');
    if (sessions.length === 0) {
        container.innerHTML = `<div class="sidebar-empty">${showingArchived ? 'No archived chats' : 'No chats yet'}</div>`;
        return;
    }

    let html = '';
    for (const s of sessions) {
        const isActive = s.id === currentSessionId;
        const date = s.last_message_at ? new Date(s.last_message_at * 1000).toLocaleDateString() : '';
        const msgCount = s.message_count || 0;
        html += `<div class="session-item${isActive ? ' active' : ''}" onclick="switchToSession(${s.id})">
            <div class="session-title">${escapeHtml(s.title || 'New Chat')}</div>
            <div class="session-meta">${msgCount} msg${msgCount !== 1 ? 's' : ''} &middot; ${date}</div>
            <div class="session-actions">
                <button onclick="event.stopPropagation(); renameSession(${s.id})" title="Rename">R</button>
                <button onclick="event.stopPropagation(); archiveSession(${s.id})" title="${showingArchived ? 'Unarchive' : 'Archive'}">A</button>
                <button onclick="event.stopPropagation(); deleteSession(${s.id})" title="Delete">X</button>
            </div>
        </div>`;
    }
    container.innerHTML = html;
}

async function createNewSession() {
    const session = await AiChatSession.create();
    currentSessionId = session.id;
    chatMessages = [];
    await loadSessions();
    renderMessages();
}

async function switchToSession(id) {
    if (id === currentSessionId) return;
    currentSessionId = id;

    const session = sessions.find(s => s.id === id);
    if (!session) return;

    const rawMessages = await session.loadMessages();

    // Convert server messages to the format renderMessages expects
    chatMessages = rawMessages.map(m => {
        const msg = { role: m.role, content: m.content };
        if (m.role === 'assistant') {
            msg.sql = m.sql || '';
            msg.error = m.error || '';
            msg.row_count = m.row_count || 0;
            // Parse JSON fields
            try { msg.columns = JSON.parse(m.columns_json || '[]'); } catch { msg.columns = []; }
            try { msg.results = JSON.parse(m.results_json || '[]'); } catch { msg.results = []; }
            // Parse multi-step queries
            try {
                const q = JSON.parse(m.queries_json || '[]');
                if (Array.isArray(q) && q.length > 0) {
                    // Parse nested columns/results if they're strings
                    msg.queries = q.map(step => ({
                        sql: step.sql || '',
                        columns: Array.isArray(step.columns) ? step.columns : [],
                        results: Array.isArray(step.results) ? step.results : [],
                        row_count: step.row_count || 0,
                        error: step.error || ''
                    }));
                }
            } catch { /* no queries */ }
            // Parse actions
            try {
                const a = JSON.parse(m.actions_json || '[]');
                if (Array.isArray(a) && a.length > 0) msg.actions = a;
            } catch { /* no actions */ }
        }
        return msg;
    });

    renderSidebar();
    renderMessages();
}

async function renameSession(id) {
    const session = sessions.find(s => s.id === id);
    if (!session) return;
    const newTitle = prompt('Rename chat:', session.title || '');
    if (newTitle === null || newTitle.trim() === '') return;
    await new AiChatSession({ id }).update({ title: newTitle.trim() });
    await loadSessions();
}

async function archiveSession(id) {
    const newArchived = showingArchived ? 0 : 1;
    await new AiChatSession({ id }).update({ archived: newArchived });
    if (id === currentSessionId) {
        currentSessionId = null;
        chatMessages = [];
        renderMessages();
    }
    await loadSessions();
}

async function deleteSession(id) {
    if (!confirm('Delete this chat and all its messages?')) return;
    await new AiChatSession({ id }).remove();
    if (id === currentSessionId) {
        currentSessionId = null;
        chatMessages = [];
        renderMessages();
    }
    await loadSessions();
}

function toggleArchiveView() {
    showingArchived = !showingArchived;
    const btn = document.getElementById('archiveToggleBtn');
    btn.textContent = showingArchived ? 'Active' : 'Archive';
    currentSessionId = null;
    chatMessages = [];
    renderMessages();
    loadSessions();
}

// ---- Messages ----

function renderMessages() {
    const container = document.getElementById('chatMessages');
    const welcome = document.getElementById('chatWelcome');

    if (!currentSessionId || chatMessages.length === 0) {
        welcome.style.display = 'block';
        container.innerHTML = '';
        return;
    }

    welcome.style.display = 'none';
    let html = '';

    for (const msg of chatMessages) {
        if (msg.role === 'user') {
            html += `<div class="chat-msg chat-msg-user">
                <div class="chat-bubble chat-bubble-user">${escapeHtml(msg.content)}</div>
            </div>`;
        } else if (msg.role === 'assistant') {
            html += renderAssistantMessage(msg);
        }
    }

    container.innerHTML = html;
    container.scrollTop = container.scrollHeight;
}

function renderQueryStep(step, label) {
    let html = '';
    if (step.error) {
        html += `<div class="chat-error">${escapeHtml(step.error)}</div>`;
    }
    if (step.sql) {
        html += `<details class="chat-details">
            <summary>${escapeHtml(label)} - SQL</summary>
            <pre class="chat-sql">${escapeHtml(step.sql)}</pre>
        </details>`;
    }
    if (step.columns && step.columns.length > 0 && step.row_count > 0) {
        const displayRows = step.results ? step.results.slice(0, 50) : [];
        html += `<details class="chat-details">
            <summary>${escapeHtml(label)} - Data (${step.row_count} row${step.row_count !== 1 ? 's' : ''})</summary>
            <div class="chat-table-wrap">
                <table class="chat-table">
                    <thead><tr>${step.columns.map(c => `<th>${escapeHtml(c)}</th>`).join('')}</tr></thead>
                    <tbody>`;
        for (const row of displayRows) {
            html += '<tr>' + row.map(cell => `<td>${escapeHtml(cell)}</td>`).join('') + '</tr>';
        }
        html += `</tbody></table>`;
        if (step.row_count > 50) {
            html += `<div class="chat-table-note">(showing first 50 of ${step.row_count} rows)</div>`;
        }
        html += `</div></details>`;
    }
    return html;
}

function renderAssistantMessage(msg) {
    let html = `<div class="chat-msg chat-msg-assistant"><div class="chat-bubble chat-bubble-assistant">`;

    if (msg.error) {
        html += `<div class="chat-error">${escapeHtml(msg.error)}</div>`;
    }

    if (msg.content) {
        html += `<div class="chat-text">${formatResponseText(msg.content)}</div>`;
    }

    // Multi-step queries display
    if (msg.queries && msg.queries.length > 1) {
        for (let i = 0; i < msg.queries.length; i++) {
            html += renderQueryStep(msg.queries[i], `Step ${i + 1}`);
        }
    } else if (msg.queries && msg.queries.length === 1) {
        // Single query — render inline like before
        html += renderQueryStep(msg.queries[0], 'Query');
    } else {
        // Fallback: old-style single sql/columns/results fields
        if (msg.sql) {
            html += `<details class="chat-details">
                <summary>Show SQL</summary>
                <pre class="chat-sql">${escapeHtml(msg.sql)}</pre>
            </details>`;
        }

        if (msg.columns && msg.columns.length > 0 && msg.row_count > 0) {
            const displayRows = msg.results ? msg.results.slice(0, 50) : [];
            html += `<details class="chat-details">
                <summary>Show data (${msg.row_count} row${msg.row_count !== 1 ? 's' : ''})</summary>
                <div class="chat-table-wrap">
                    <table class="chat-table">
                        <thead><tr>${msg.columns.map(c => `<th>${escapeHtml(c)}</th>`).join('')}</tr></thead>
                        <tbody>`;
            for (const row of displayRows) {
                html += '<tr>' + row.map(cell => `<td>${escapeHtml(cell)}</td>`).join('') + '</tr>';
            }
            html += `</tbody></table>`;
            if (msg.row_count > 50) {
                html += `<div class="chat-table-note">(showing first 50 of ${msg.row_count} rows)</div>`;
            }
            html += `</div></details>`;
        }
    }

    // Navigation action buttons
    if (msg.actions && msg.actions.length > 0) {
        html += `<div class="chat-actions">`;
        for (const action of msg.actions) {
            if (action.type === 'navigate' && action.label && action.url) {
                const filtersAttr = action.filters ? ` data-nav-filters="${escapeHtml(JSON.stringify(action.filters))}"` : '';
                html += `<button class="chat-action-btn" data-nav-url="${escapeHtml(action.url)}"${filtersAttr}>
                    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="14" height="14"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
                    ${escapeHtml(action.label)}
                </button>`;
            }
        }
        html += `</div>`;
    }

    html += `</div></div>`;
    return html;
}

function formatResponseText(text) {
    let escaped = escapeHtml(text);
    escaped = escaped.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>');
    escaped = escaped.replace(/`([^`]+)`/g, '<code>$1</code>');
    escaped = escaped.replace(/\n/g, '<br>');
    return escaped;
}

function showThinking() {
    const container = document.getElementById('chatMessages');
    const indicator = document.createElement('div');
    indicator.id = 'thinkingIndicator';
    indicator.className = 'chat-msg chat-msg-assistant';
    indicator.innerHTML = `<div class="chat-bubble chat-bubble-assistant chat-thinking">Thinking<span class="thinking-dots"></span></div>`;
    container.appendChild(indicator);
    container.scrollTop = container.scrollHeight;
}

function hideThinking() {
    const indicator = document.getElementById('thinkingIndicator');
    if (indicator) indicator.remove();
}

async function sendMessage(text) {
    if (!text || !text.trim() || isWaiting) return;

    const content = text.trim();

    // Auto-create session if none selected
    if (!currentSessionId) {
        const session = await AiChatSession.create();
        currentSessionId = session.id;
        await loadSessions();
    }

    chatMessages.push({ role: 'user', content });
    renderMessages();

    const input = document.getElementById('chatInput');
    input.value = '';
    input.focus();

    isWaiting = true;
    document.getElementById('chatSendBtn').disabled = true;
    showThinking();

    try {
        // Build messages array for API (only role + content)
        const apiMessages = chatMessages
            .filter(m => m.role === 'user' || m.role === 'assistant')
            .map(m => ({ role: m.role, content: m.content }));

        const res = await fetch('/api/chat', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                messages: apiMessages,
                session_id: currentSessionId,
                context_doc_ids: getSelectedDocIds()
            })
        });

        const data = await res.json();

        const assistantMsg = {
            role: 'assistant',
            content: data.response || '',
            sql: data.sql || '',
            columns: data.columns || [],
            results: data.results || [],
            row_count: data.row_count || 0,
            error: data.error || ''
        };

        // Multi-step queries
        if (data.queries && Array.isArray(data.queries) && data.queries.length > 0) {
            assistantMsg.queries = data.queries;
        }

        // Navigation actions
        if (data.actions && Array.isArray(data.actions) && data.actions.length > 0) {
            assistantMsg.actions = data.actions;
        }

        chatMessages.push(assistantMsg);
    } catch (e) {
        chatMessages.push({
            role: 'assistant',
            content: '',
            error: 'Network error: ' + e.message,
            sql: '', columns: [], results: [], row_count: 0
        });
    } finally {
        isWaiting = false;
        document.getElementById('chatSendBtn').disabled = false;
        hideThinking();
        renderMessages();
        // Refresh sidebar to show updated message count / title
        loadSessions();
    }
}

function askExample(text) {
    document.getElementById('chatInput').value = text;
    sendMessage(text);
}

// ---- localStorage migration ----

async function migrateLocalStorage() {
    const STORAGE_KEY = 'chat_history';
    const MIGRATED_FLAG = 'chat_history_migrated';

    if (localStorage.getItem(MIGRATED_FLAG)) return;

    let oldMessages;
    try {
        const stored = localStorage.getItem(STORAGE_KEY);
        if (!stored) {
            localStorage.setItem(MIGRATED_FLAG, '1');
            return;
        }
        oldMessages = JSON.parse(stored);
        if (!Array.isArray(oldMessages) || oldMessages.length === 0) {
            localStorage.setItem(MIGRATED_FLAG, '1');
            return;
        }
    } catch {
        localStorage.setItem(MIGRATED_FLAG, '1');
        return;
    }

    try {
        // Create a session for the migrated messages
        const session = await AiChatSession.create('Migrated Chat');
        await session.importMessages(oldMessages);

        // Clean up
        localStorage.removeItem(STORAGE_KEY);
        localStorage.setItem(MIGRATED_FLAG, '1');

        console.log('Migrated', oldMessages.length, 'messages from localStorage to session', session.id);
    } catch (e) {
        console.warn('localStorage migration failed:', e);
    }
}

// ---- Context doc selector (above chat input) ----

async function refreshContextDocStrip() {
    const strip = document.getElementById('contextDocStrip');
    if (!strip) return;
    // Load docs if not already loaded
    if (!allDocs.length) {
        allDocs = await ChatContextDoc.listDocs();
    }
    if (!allDocs.length) {
        strip.innerHTML = '';
        return;
    }
    let html = '<span class="ctx-label">Context:</span>';

    // Built-in docs first
    const builtinDocs = allDocs.filter(d => d.is_builtin);
    const userDocs = allDocs.filter(d => !d.is_builtin);

    for (const doc of builtinDocs) {
        const isSel = selectedDocIds.has(doc.id);
        const sel = isSel ? ' selected' : '';
        const icon = isSel ? '&#10003; ' : '&#9733; ';
        html += `<span class="ctx-doc-badge builtin${sel}" onclick="toggleContextDoc('${escapeHtml(doc.id)}')" title="${escapeHtml(doc.description || doc.title || 'Untitled')}">${icon}${escapeHtml(doc.title || 'Untitled')}</span>`;
    }
    for (const doc of userDocs) {
        const isSel = selectedDocIds.has(doc.id);
        const sel = isSel ? ' selected' : '';
        const icon = isSel ? '&#10003; ' : '';
        html += `<span class="ctx-doc-badge${sel}" onclick="toggleContextDoc(${doc.id})" title="${escapeHtml(doc.title || 'Untitled')}">${icon}${escapeHtml(doc.title || 'Untitled')}</span>`;
    }

    // Onboarding hint (shown once)
    html += `<span id="contextDocHint"><span>Click a report to give the AI extra context for better answers.</span><span class="hint-dismiss" onclick="dismissContextDocHint()" title="Dismiss">&times;</span></span>`;

    strip.innerHTML = html;

    // Show hint if not yet dismissed
    showContextDocHintIfNeeded();
}

async function showContextDocHintIfNeeded() {
    const hint = document.getElementById('contextDocHint');
    if (!hint) return;
    const dismissed = await ConfigEntry.get('seen_context_doc_hint');
    if (dismissed === '1') {
        hint.style.display = 'none';
    } else {
        hint.style.display = 'flex';
    }
}

async function dismissContextDocHint() {
    await ConfigEntry.set('seen_context_doc_hint', '1');
    const hint = document.getElementById('contextDocHint');
    if (hint) hint.style.display = 'none';
}

function toggleContextDoc(docId) {
    const wasSelected = selectedDocIds.has(docId);
    if (wasSelected) {
        selectedDocIds.delete(docId);
    } else {
        selectedDocIds.add(docId);
    }
    refreshContextDocStrip();

    // Auto-dismiss onboarding hint on first toggle
    if (!wasSelected) {
        const hint = document.getElementById('contextDocHint');
        if (hint && hint.style.display !== 'none') {
            dismissContextDocHint();
        }
    }

    // Brief toast feedback
    const doc = allDocs.find(d => String(d.id) === String(docId));
    const name = doc ? (doc.title || 'Report') : 'Report';
    lib.showToast(wasSelected ? `"${name}" deactivated` : `"${name}" activated — AI will use this context`, 2000);
}

function getSelectedDocIds() {
    return Array.from(selectedDocIds);
}

// ---- Documents tab ----

let allDocs = [];
let currentDocId = null;
let currentSidebarTab = 'chats';

function switchSidebarTab(tab) {
    currentSidebarTab = tab;
    document.querySelectorAll('#sidebarTabs button').forEach(b => b.classList.remove('active'));
    document.querySelector(`#sidebarTabs button[data-tab="${tab}"]`).classList.add('active');

    document.getElementById('chatsSidebarContent').style.display = tab === 'chats' ? '' : 'none';
    document.getElementById('docsSidebarContent').style.display = tab === 'documents' ? '' : 'none';

    if (tab === 'documents') {
        loadDocs();
        document.getElementById('contextDocStrip').style.display = 'none';
        if (!currentDocId) {
            showDocWelcome();
        }
    } else {
        // Restore chat view
        currentDocId = null;
        renderMessages();
        document.getElementById('chatInputBar').style.display = '';
        document.getElementById('contextDocStrip').style.display = '';
        refreshContextDocStrip();
    }
}

function showDocWelcome() {
    const container = document.getElementById('chatMessages');
    const welcome = document.getElementById('chatWelcome');
    welcome.style.display = 'none';
    document.getElementById('chatInputBar').style.display = 'none';
    container.innerHTML = `<div style="text-align:center;padding:40px 20px;">
        <h2>Context Documents</h2>
        <p style="color:var(--color-text-secondary);">Create and manage markdown documents that can be used as context in AI conversations.</p>
        <p style="color:var(--color-text-disabled);font-size:13px;margin-top:10px;">Select a document from the sidebar or create a new one.</p>
    </div>`;
}

async function loadDocs() {
    allDocs = await ChatContextDoc.listDocs();
    renderDocList();
    refreshContextDocStrip();
}

function renderDocList() {
    const container = document.getElementById('docList');
    if (!allDocs.length) {
        container.innerHTML = `<div class="sidebar-empty">No documents yet</div>`;
        return;
    }

    const builtinDocs = allDocs.filter(d => d.is_builtin);
    const userDocs = allDocs.filter(d => !d.is_builtin);

    let html = '';

    // Built-in templates section
    if (builtinDocs.length > 0) {
        html += `<div class="doc-section-header">Built-in Templates</div>`;
        for (const doc of builtinDocs) {
            const isActive = doc.id === currentDocId;
            html += `<div class="doc-item builtin${isActive ? ' active' : ''}" onclick="selectDoc('${escapeHtml(doc.id)}')">
                <div class="doc-item-title">&#9733; ${escapeHtml(doc.title || 'Untitled')}</div>
                <div class="doc-item-meta">${escapeHtml(doc.description || '')}</div>
            </div>`;
        }
    }

    // User documents section
    html += `<div class="doc-section-header">Your Documents</div>`;
    if (userDocs.length === 0) {
        html += `<div class="sidebar-empty" style="padding:10px;">No custom documents yet</div>`;
    }
    for (const doc of userDocs) {
        const isActive = doc.id === currentDocId;
        const updated = doc.updated_at ? new Date(doc.updated_at * 1000).toLocaleDateString() : '';
        const preview = (doc.content || '').substring(0, 40);
        html += `<div class="doc-item${isActive ? ' active' : ''}" onclick="selectDoc(${doc.id})">
            <div class="doc-item-title">${escapeHtml(doc.title || 'Untitled')}</div>
            <div class="doc-item-meta">${updated}${preview ? ' &middot; ' + escapeHtml(preview) : ''}</div>
            <div class="doc-item-actions">
                <button onclick="event.stopPropagation(); deleteDoc(${doc.id})" title="Delete">X</button>
            </div>
        </div>`;
    }
    container.innerHTML = html;
}

async function createNewDoc() {
    const doc = new ChatContextDoc({ title: 'New Document', content: '', sort_order: allDocs.length });
    const res = await doc.save();
    if (res.ok) {
        await loadDocs();
        selectDoc(res.data.id);
    }
}

function selectDoc(docId) {
    currentDocId = docId;
    renderDocList();
    const doc = allDocs.find(d => d.id === docId);
    if (!doc) return;

    const welcome = document.getElementById('chatWelcome');
    welcome.style.display = 'none';
    document.getElementById('chatInputBar').style.display = 'none';

    const container = document.getElementById('chatMessages');

    if (doc.is_builtin) {
        // Read-only view for built-in docs
        container.innerHTML = `<div id="docEditorPanel">
            <div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;">
                <span style="font-size:16px;font-weight:bold;color:var(--color-text-primary);">${escapeHtml(doc.title || 'Untitled')}</span>
                <span class="builtin-badge">Built-in</span>
            </div>
            <div style="font-size:12px;color:var(--color-text-secondary);margin-bottom:10px;">${escapeHtml(doc.description || '')}</div>
            <textarea id="docContentInput" readonly style="flex:1;background:var(--color-bg-primary);color:var(--color-text-primary);border:1px solid var(--color-border);border-radius:var(--radius-sm);padding:10px 12px;resize:none;font-family:monospace;font-size:13px;line-height:1.5;outline:none;cursor:default;">${escapeHtml(doc.content || '')}</textarea>
            <div id="docEditorActions">
                <button onclick="cloneBuiltinDoc('${escapeHtml(doc.id)}')">Clone to Editable Copy</button>
            </div>
        </div>`;
    } else {
        // Editable view for user docs
        container.innerHTML = `<div id="docEditorPanel">
            <input type="text" id="docTitleInput" value="${escapeHtml(doc.title || '')}" placeholder="Document title">
            <textarea id="docContentInput" placeholder="Write markdown content...">${escapeHtml(doc.content || '')}</textarea>
            <div id="docEditorActions">
                <button onclick="saveCurrentDoc()">Save</button>
                <button onclick="deleteDoc(${doc.id})" style="background:#4a1a1a;color:var(--color-error);">Delete</button>
            </div>
        </div>`;
    }
}

async function cloneBuiltinDoc(builtinId) {
    const doc = allDocs.find(d => d.id === builtinId);
    if (!doc) return;

    const newDoc = new ChatContextDoc({
        title: (doc.title || 'Untitled') + ' (Copy)',
        content: doc.content || '',
        sort_order: allDocs.length
    });
    const res = await newDoc.save();
    if (res.ok) {
        await loadDocs();
        selectDoc(res.data.id);
    }
}

async function saveCurrentDoc() {
    if (!currentDocId) return;
    const doc = allDocs.find(d => d.id === currentDocId);
    if (!doc || doc.is_builtin) return;
    const title = document.getElementById('docTitleInput').value.trim();
    const content = document.getElementById('docContentInput').value;
    const updated = new ChatContextDoc({
        ...doc,
        title: title || 'Untitled',
        content
    });
    updated.id = currentDocId;
    const res = await updated.save();
    if (res.ok) {
        await loadDocs();
    }
}

async function deleteDoc(docId) {
    if (!confirm('Delete this document?')) return;
    const doc = new ChatContextDoc();
    doc.id = docId;
    await doc.delete();
    selectedDocIds.delete(docId);
    if (docId === currentDocId) {
        currentDocId = null;
        showDocWelcome();
    }
    await loadDocs();
}

// ---- Init ----

async function initChat() {
    await migrateLocalStorage();
    await loadSessions();
    await refreshContextDocStrip();

    // Auto-select most recent session
    if (sessions.length > 0 && !currentSessionId) {
        await switchToSession(sessions[0].id);
    } else {
        renderMessages();
    }

    const input = document.getElementById('chatInput');
    input.addEventListener('keydown', (e) => {
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            sendMessage(input.value);
        }
    });

    document.getElementById('chatSendBtn').addEventListener('click', () => {
        sendMessage(input.value);
    });

    document.getElementById('newChatBtn').addEventListener('click', () => {
        createNewSession();
    });

    document.getElementById('archiveToggleBtn').addEventListener('click', () => {
        toggleArchiveView();
    });

    // Delegated click handler for navigation action buttons
    document.getElementById('chatMessages').addEventListener('click', (e) => {
        const btn = e.target.closest('.chat-action-btn');
        if (!btn) return;
        const url = btn.getAttribute('data-nav-url');
        if (!url) return;
        const filtersStr = btn.getAttribute('data-nav-filters');
        let filters = null;
        if (filtersStr) { try { filters = JSON.parse(filtersStr); } catch(_) {} }
        lib.navigateWithFilter(url, filters);
    });
}
