/**
 * History Component
 * Manages the display and interaction with research history
 */
(function() {
    // DOM Elements
    let historyContainer = null;
    let searchInput = null;
    let clearHistoryBtn = null;
    let historyEmptyMessage = null;

    // Component state
    let historyItems = [];
    let filteredItems = [];
    let inputDebounceTimer = null;
    let semanticDebounceTimer = null;
    const SM = (typeof LDR_CONSTANTS !== 'undefined' && LDR_CONSTANTS.SEARCH_MODE) || { HYBRID: 'hybrid', TEXT: 'text', SEMANTIC: 'semantic' };
    let searchMode = SM.HYBRID;
    let hybridSearchId = 0;
    let semanticSearchId = 0;

    // Security: local escapeHtml to prevent XSS in innerHTML assignments
    // bearer:disable javascript_lang_manual_html_sanitization
    const esc = window.escapeHtml || (s => String(s || '').replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":"&#39;"})[m]));

    // Shared semantic search utilities (from semantic_search.js)
    const renderSnippet = (window.SemanticSearch && window.SemanticSearch.renderSnippet) || (md => esc(md || ''));

    // Fallback UI utilities in case main UI utils aren't loaded
    const uiUtils = {
        showSpinner(container, message) {
            if (window.ui && window.ui.showSpinner) {
                window.ui.showSpinner(container, message);
                return;
            }

            // Fallback implementation
            if (!container) container = document.body;
            // Security: escapeHtml applied to message before innerHTML insertion
            const spinnerHtml = `
                <div class="ldr-loading-spinner ldr-centered">
                    <div class="ldr-spinner"></div>
                    ${message ? `<div class="ldr-spinner-message">${esc(message)}</div>` : ''}
                </div>
            `;
            // bearer:disable javascript_lang_dangerous_insert_html
            // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: variable built from escaped/numeric values above
            container.innerHTML = spinnerHtml;
        },

        hideSpinner(container) {
            if (window.ui && window.ui.hideSpinner) {
                window.ui.hideSpinner(container);
                return;
            }

            // Fallback implementation
            if (!container) container = document.body;
            const spinner = container.querySelector('.ldr-loading-spinner');
            if (spinner) {
                spinner.remove();
            }
        },

        showError(message) {
            // window.ui.showError(container, message) expects a container id as
            // its first arg; passing the message there silently renders nothing.
            // Route through showMessage(message, 'error') which is the toast API
            // used everywhere else (and whose first arg is the message).
            if (window.ui && window.ui.showMessage) {
                window.ui.showMessage(message, 'error');
                return;
            }

            // Fallback implementation
            SafeLogger.error(message);
            alert(message);
        },

        showMessage(message) {
            if (window.ui && window.ui.showMessage) {
                window.ui.showMessage(message);
                return;
            }

            // Fallback implementation
            SafeLogger.log(message);
            alert(message);
        }
    };

    // Fallback API utilities
    const apiUtils = {
        async getResearchHistory() {
            if (window.api && window.api.getResearchHistory) {
                return window.api.getResearchHistory();
            }

            // Fallback implementation
            try {
                const response = await fetch(URLS.API.HISTORY);
                if (!response.ok) {
                    throw new Error(`API Error: ${response.status} ${response.statusText}`);
                }
                return await response.json();
            } catch (error) {
                SafeLogger.error('API Error:', error);
                throw error;
            }
        },

        async deleteResearch(researchId) {
            if (window.api && window.api.deleteResearch) {
                return window.api.deleteResearch(researchId);
            }

            // Fallback implementation
            try {
                const csrfToken = window.api ? window.api.getCsrfToken() : '';
                const response = await fetch(URLBuilder.deleteResearch(researchId), {
                    method: 'DELETE',
                    headers: {
                        ...(csrfToken ? { 'X-CSRFToken': csrfToken } : {})
                    }
                });
                if (!response.ok) {
                    throw new Error(`API Error: ${response.status} ${response.statusText}`);
                }
                return await response.json();
            } catch (error) {
                SafeLogger.error('API Error:', error);
                throw error;
            }
        },

        async clearResearchHistory() {
            if (window.api && window.api.clearResearchHistory) {
                return window.api.clearResearchHistory();
            }

            // Fallback implementation
            try {
                const csrfToken = window.api ? window.api.getCsrfToken() : '';
                const response = await fetch(URLS.API.CLEAR_HISTORY, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        ...(csrfToken ? { 'X-CSRFToken': csrfToken } : {})
                    },
                    body: JSON.stringify({})
                });
                if (!response.ok) {
                    throw new Error(`API Error: ${response.status} ${response.statusText}`);
                }
                return await response.json();
            } catch (error) {
                SafeLogger.error('API Error:', error);
                throw error;
            }
        }
    };

    /**
     * Initialize the history component
     */
    function initializeHistory() {
        // Get DOM elements
        historyContainer = document.getElementById('history-items');
        searchInput = document.getElementById('history-search');
        clearHistoryBtn = document.getElementById('clear-history-btn');
        historyEmptyMessage = document.getElementById('history-empty-message');

        if (!historyContainer) {
            SafeLogger.error('Required DOM elements not found for history component');
            return;
        }

        // Set up event listeners
        setupEventListeners();

        // Load history data
        loadHistoryData();

        SafeLogger.log('History component initialized');
    }

    /**
     * Set up event listeners
     */
    function setupEventListeners() {
        // Debounced search input
        if (searchInput) {
            searchInput.addEventListener('input', () => {
                clearTimeout(inputDebounceTimer);
                inputDebounceTimer = setTimeout(handleSearchInput, 250);
            });
        }

        // Search mode dropdown
        const modeMenu = document.getElementById('search-mode-menu');
        if (modeMenu) {
            modeMenu.addEventListener('click', (e) => {
                const item = e.target.closest('.dropdown-item');
                if (!item) return;
                e.preventDefault();

                const mode = item.dataset.mode;
                if (!mode || mode === searchMode) return;

                searchMode = mode;

                // Update active state
                modeMenu.querySelectorAll('.dropdown-item').forEach(el => el.classList.remove('active'));
                item.classList.add('active');

                // Update button label
                const btn = document.getElementById('search-mode-btn');
                const iconMap = { hybrid: 'fa-brain', text: 'fa-font', semantic: 'fa-brain' };
                const labelMap = { hybrid: 'AI Hybrid', text: 'Text Only', semantic: 'AI Only' };
                const placeholders = { hybrid: 'Search titles + content...', text: 'Filter history by title...', semantic: 'Search content with AI...' };
                if (btn && labelMap[mode]) {
                    window.safeUpdateButton(btn, iconMap[mode], ' ' + labelMap[mode]);
                }
                if (searchInput) searchInput.placeholder = placeholders[mode];

                handleSearchInput();
            });
        }

        // Clear history button
        if (clearHistoryBtn) {
            clearHistoryBtn.addEventListener('click', handleClearHistory);
        }

        // Single delegated handler for all history item interactions
        if (historyContainer) {
            // Toggle handler for chat group expand/collapse
            historyContainer.addEventListener('click', function(e) {
                const toggleBtn = e.target.closest('.ldr-group-toggle');
                if (!toggleBtn) return;
                e.stopPropagation();
                const groupEl = toggleBtn.closest('.ldr-history-group');
                if (!groupEl) return;
                const childrenContainer = groupEl.querySelector('.ldr-history-group-children');
                if (!childrenContainer) return;
                const isExpanded = toggleBtn.classList.toggle('ldr-group-expanded');
                toggleBtn.setAttribute('aria-expanded', isExpanded);
                childrenContainer.classList.toggle('ldr-history-group-children--open', isExpanded);
            });

            historyContainer.addEventListener('click', function(e) {
                if (e.target.closest('.ldr-group-toggle')) return;
                const itemEl = e.target.closest('.ldr-history-item');
                if (!itemEl) return;
                const itemId = itemEl.dataset.id;
                const isChatItem = itemEl.dataset.type === 'chat';
                const itemData = findItemById(itemId);

                // For semantic-only items not in historyItems, handle View + item click
                if (!itemData) {
                    if (e.target.closest('.ldr-view-btn') || !e.target.closest('button')) {
                        URLValidator.safeAssign(window.location, 'href', URLBuilder.resultsPage(itemId));
                    }
                    return;
                }

                // A research item nested under a chat group carries its
                // parent chat_session_id in metadata. Clicking such a
                // nested item should return the user to the chat
                // conversation (preserving the grouped-UI affordance)
                // rather than deep-linking to the isolated research
                // results page.
                const parentChatId = itemData.metadata && itemData.metadata.chat_session_id;
                const isChatChild = !itemData._is_chat && !isChatItem && !!parentChatId;

                if (e.target.closest('.ldr-delete-item-btn')) {
                    handleDeleteItem(itemId, isChatItem);
                } else if (e.target.closest('.ldr-view-btn')) {
                    if (isChatItem || itemData._is_chat) {
                        URLValidator.safeAssign(window.location, 'href', `/chat/${encodeURIComponent(itemId)}`);
                    } else if (isChatChild) {
                        URLValidator.safeAssign(window.location, 'href', `/chat/${encodeURIComponent(parentChatId)}`);
                    } else {
                        URLValidator.safeAssign(window.location, 'href', URLBuilder.resultsPage(itemId));
                    }
                } else if (e.target.closest('.ldr-library-btn')) {
                    URLValidator.safeAssign(window.location, 'href', `${URLS.PAGES.LIBRARY}?research=${encodeURIComponent(itemId)}`);
                } else if (e.target.closest('.ldr-subscribe-btn')) {
                    handleSubscribe(itemData);
                } else if (e.target.closest('.ldr-rerun-btn')) {
                    handleRerun(itemData);
                } else if (e.target.closest('.ldr-copy-query-btn')) {
                    handleCopyQuery(itemData);
                } else if (isChatItem || itemData._is_chat) {
                    // Item-level click on a chat session
                    URLValidator.safeAssign(window.location, 'href', `/chat/${encodeURIComponent(itemId)}`);
                } else if (isChatChild) {
                    // Item-level click on a research nested under a chat
                    // group: return to the parent chat conversation.
                    URLValidator.safeAssign(window.location, 'href', `/chat/${encodeURIComponent(parentChatId)}`);
                } else if (ResearchStates.isCompleted(itemData.status)) {
                    URLValidator.safeAssign(window.location, 'href', URLBuilder.resultsPage(itemId));
                } else {
                    URLValidator.safeAssign(window.location, 'href', URLBuilder.progressPage(itemId));
                }
            });
        }
    }

    async function fetchChatSessions({ all = false } = {}) {
        // Paginate through ALL chat sessions when `all=true` (used by the
        // Clear-All path so it actually deletes every session, not just
        // the first page). Without pagination, users with >50 sessions
        // saw Clear All succeed visually but old sessions reappeared on
        // next reload because they were never fetched, hence never
        // DELETE'd.
        try {
            const csrfToken = window.api ? window.api.getCsrfToken() : '';
            const PAGE_SIZE = 100;  // server max
            const collected = [];
            let offset = 0;
            // Hard safety cap so a misbehaving server can't loop forever.
            const HARD_CAP = 10_000;
            // eslint-disable-next-line no-constant-condition
            while (true) {
                const url =
                    `/api/chat/sessions?status=all&limit=${PAGE_SIZE}&offset=${offset}`;
                const response = await fetch(url, {
                    headers: {
                        ...(csrfToken ? { 'X-CSRFToken': csrfToken } : {})
                    }
                });
                if (!response.ok) break;
                const data = await response.json();
                if (!data.success || !Array.isArray(data.sessions)) break;
                for (const s of data.sessions) {
                    collected.push({
                        id: s.id,
                        query: s.title || 'Chat Session',
                        title: s.title,
                        mode: 'chat',
                        status: s.status === 'active' ? 'completed' : s.status,
                        created_at: s.created_at,
                        _is_chat: true
                    });
                }
                if (!all) break;
                if (data.sessions.length < PAGE_SIZE) break;
                offset += PAGE_SIZE;
                if (offset >= HARD_CAP) break;
            }
            return collected;
        } catch (e) {
            SafeLogger.warn('Could not fetch chat sessions:', e);
            return [];
        }
    }

    function groupItemsByChatSession(items) {
        const chatSessionMap = new Map();
        items.forEach(item => {
            if (item._is_chat) {
                chatSessionMap.set(item.id, item);
                item._children = [];
            }
        });
        const topLevelItems = [];
        items.forEach(item => {
            if (item._is_chat) {
                topLevelItems.push(item);
            } else {
                const chatId = item.metadata && item.metadata.chat_session_id;
                if (chatId && chatSessionMap.has(chatId)) {
                    chatSessionMap.get(chatId)._children.push(item);
                } else {
                    topLevelItems.push(item);
                }
            }
        });
        // Sort children first (oldest → newest within the group), then
        // sort the top-level by max(child.created_at, self.created_at) so a
        // chat session with a recent research run floats to the top instead
        // of staying anchored to its (possibly older) session.created_at.
        topLevelItems.forEach(item => {
            if (item._children && item._children.length > 0) {
                item._children.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
                const lastChild = item._children[item._children.length - 1];
                const lastChildDate = new Date(lastChild.created_at);
                const ownDate = new Date(item.created_at);
                item._sortDate = lastChildDate > ownDate ? lastChildDate : ownDate;
            } else {
                item._sortDate = new Date(item.created_at);
            }
        });
        topLevelItems.sort((a, b) => b._sortDate - a._sortDate);
        return topLevelItems;
    }

    function findItemById(id) {
        for (const item of historyItems) {
            if (String(item.id) === String(id)) return item;
            if (item._children) {
                const child = item._children.find(c => String(c.id) === String(id));
                if (child) return child;
            }
        }
        return null;
    }

    /**
     * Load history data from API
     */
    async function loadHistoryData() {
        // Show loading state
        uiUtils.showSpinner(historyContainer, 'Loading research history...');

        try {
            // Get history items and chat sessions in parallel
            const [response, chatSessions] = await Promise.all([
                apiUtils.getResearchHistory(),
                fetchChatSessions()
            ]);

            if (response && Array.isArray(response.items)) {
                const merged = [...response.items, ...chatSessions];
                merged.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
                historyItems = groupItemsByChatSession(merged);
                filteredItems = [...historyItems];

                // Render history items
                renderHistoryItems();
            } else {
                throw new Error('Invalid response format');
            }
        } catch (error) {
            SafeLogger.error('Error loading history:', error);
            uiUtils.hideSpinner(historyContainer);
            uiUtils.showError('Error loading history: ' + error.message);
        }
    }

    /**
     * Render history items
     */
    function renderHistoryItems() {
        // Hide spinner
        uiUtils.hideSpinner(historyContainer);

        // Clear container
        historyContainer.innerHTML = '';

        // Show empty message if no items
        if (filteredItems.length === 0) {
            if (historyEmptyMessage) {
                historyEmptyMessage.style.display = 'block';
            } else {
                // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: all interpolations use escapeHtml/esc, numeric coercion, or hardcoded strings
                historyContainer.innerHTML = `
                    <div class="ldr-empty-state">
                        <i class="fas fa-history ldr-empty-icon"></i>
                        <p>No research history found.</p>
                        ${searchInput && searchInput.value ? '<p>Try adjusting your search query.</p>' : ''}
                    </div>
                `;
            }

            if (clearHistoryBtn) {
                clearHistoryBtn.style.display = 'none';
            }
            return;
        }

        // Hide empty message
        if (historyEmptyMessage) {
            historyEmptyMessage.style.display = 'none';
        }

        // Show clear button
        if (clearHistoryBtn) {
            clearHistoryBtn.style.display = 'inline-block';
        }

        // Create items using DocumentFragment for batch DOM insertion
        const fragment = document.createDocumentFragment();
        filteredItems.forEach(item => {
            fragment.appendChild(createHistoryItemElement(item));
        });
        historyContainer.appendChild(fragment);
    }

    /**
     * Format date safely using the formatter if available
     */
    function formatDate(dateStr) {
        if (window.formatting && window.formatting.formatDate) {
            return window.formatting.formatDate(dateStr);
        }

        // Simple fallback date formatting
        try {
            const date = new Date(dateStr);
            return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
        } catch {
            return dateStr;
        }
    }

    /**
     * Format status safely using ResearchStates helper
     */
    function formatStatus(status) {
        return ResearchStates.formatStatus(status);
    }

    /**
     * Format mode safely using the formatter if available
     */
    function formatMode(mode) {
        if (window.formatting && window.formatting.formatMode) {
            return window.formatting.formatMode(mode);
        }

        // Simple fallback formatting
        const modeMap = {
            'quick': 'Quick Summary',
            'detailed': 'Detailed Report'
        };

        return modeMap[mode] || mode;
    }

    /**
     * Create a history item element
     * @param {Object} item - The history item data
     * @param {Object|null} semanticMatch - Optional semantic match data {similarity, snippet}
     * @returns {HTMLElement} The history item element
     */
    function createHistoryItemElement(item, semanticMatch) {
        const isChatItem = item._is_chat || item.mode === 'chat';
        const hasChildren = isChatItem && item._children && item._children.length > 0;
        if (hasChildren) {
            return createChatGroupElement(item);
        }

        const itemEl = document.createElement('div');
        itemEl.className = 'ldr-history-item';
        if (semanticMatch) itemEl.classList.add('ldr-history-item--semantic');
        if (isChatItem) itemEl.dataset.type = 'chat';
        itemEl.dataset.id = item.id;

        // Format date
        const formattedDate = formatDate(item.created_at);

        // Get a display title (use query if title is not available)
        const displayTitle = item.title || formatTitleFromQuery(item.query);

        // Status class - convert in_progress to in-progress for CSS
        const statusClass = item.status ? item.status.replace('_', '-') : '';

        // Check if this is a news-related research
        const isNewsItem = item.metadata && item.metadata.is_news_search;

        // Chat indicator badge
        const chatBadgeHtml = isChatItem ? '<span class="ldr-chat-indicator"><i class="fas fa-comments"></i> Chat</span>' : '';

        // AI match badge + snippet rows (for Tier 1 items)
        const aiMatchHtml = semanticMatch ? `
            <div class="ldr-history-item-ai-match">
                <span class="ldr-ai-match-badge"><i class="fas fa-brain"></i> ${esc(String(semanticMatch.similarity))}% match</span>
            </div>
            ${semanticMatch.snippet ? `<div class="ldr-history-item-snippet">${renderSnippet(semanticMatch.snippet, searchInput ? searchInput.value.trim() : '')}</div>` : ''}
        ` : '';

        // bearer:disable javascript_lang_dangerous_insert_html
        // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: variable built from escaped/numeric values above
        itemEl.innerHTML = `
            <div class="ldr-history-item-header">
                <div class="ldr-history-item-title">${esc(displayTitle)}</div>
                ${chatBadgeHtml}
                <div class="ldr-history-item-status ldr-status-${esc(statusClass)}">${esc(formatStatus(item.status))}</div>
            </div>
            ${aiMatchHtml}
            <div class="ldr-history-item-meta">
                <div class="ldr-history-item-date">${esc(formattedDate)}</div>
                <div class="ldr-history-item-mode">${esc(formatMode(item.mode))}</div>
                ${isNewsItem ? '<span class="ldr-news-indicator"><i class="fas fa-newspaper"></i> News</span>' : ''}
            </div>
            <div class="ldr-history-item-actions">
                ${isChatItem ?
                    `<button class="btn btn-sm ldr-btn-outline ldr-view-btn">
                        <i class="fas fa-comments"></i><span> Open Chat</span>
                    </button>` :
                    (ResearchStates.isCompleted(item.status) ?
                    `<button class="btn btn-sm ldr-btn-outline ldr-view-btn">
                        <i class="fas fa-eye"></i><span> View</span>
                    </button>` : '')}
                ${!isChatItem && ResearchStates.isCompleted(item.status) && item.document_count > 0 ?
                    `<button class="btn btn-sm ldr-btn-outline ldr-library-btn">
                        <i class="fas fa-book"></i><span> Library (${esc(String(item.document_count))})</span>
                    </button>` : ''}
                ${!isChatItem && isNewsItem && ResearchStates.isCompleted(item.status) ?
                    `<button class="btn btn-sm ldr-btn-outline ldr-subscribe-btn" data-research-id="${esc(item.id)}" data-query="${esc(encodeURIComponent(item.query))}">
                        <i class="fas fa-bell"></i><span> Subscribe</span>
                    </button>` : ''}
                ${!isChatItem && ResearchStates.isTerminal(item.status) ?
                    `<button class="btn btn-sm ldr-btn-outline ldr-rerun-btn" title="Re-run this research">
                        <i class="fas fa-redo"></i><span> Re-run</span>
                    </button>` : ''}
                ${!isChatItem && item.query ?
                    `<button class="btn btn-sm ldr-btn-outline ldr-copy-query-btn" title="Copy query" aria-label="Copy query">
                        <i class="fas fa-copy"></i>
                    </button>` : ''}
                <button class="btn btn-sm ldr-btn-outline ldr-delete-item-btn" title="Delete" aria-label="Delete">
                    <i class="fas fa-trash-alt"></i>
                </button>
            </div>
        `;

        return itemEl;
    }

    /**
     * Create a collapsible chat group element with children
     * @param {Object} item - The chat session item with _children
     * @returns {HTMLElement} The chat group element
     */
    function createChatGroupElement(item) {
        const groupEl = document.createElement('div');
        groupEl.className = 'ldr-history-group';
        groupEl.dataset.id = item.id;
        groupEl.dataset.type = 'chat';

        const formattedDate = formatDate(item.created_at);
        const displayTitle = item.title || formatTitleFromQuery(item.query);
        const childCount = item._children ? item._children.length : 0;
        const isExpanded = item._forceExpanded || false;

        // bearer:disable javascript_lang_dangerous_insert_html
        // eslint-disable-next-line no-unsanitized/property -- audited: variable built from escaped/numeric values
        groupEl.innerHTML = `
            <div class="ldr-history-item ldr-history-group-header" data-id="${esc(String(item.id))}" data-type="chat">
                <div class="ldr-history-item-header">
                    <button class="btn btn-sm ldr-group-toggle${isExpanded ? ' ldr-group-expanded' : ''}" aria-expanded="${isExpanded}">
                        <i class="fas fa-chevron-right"></i>
                    </button>
                    <div class="ldr-history-item-title">${esc(displayTitle)}</div>
                    <span class="ldr-chat-indicator"><i class="fas fa-comments"></i> Chat</span>
                    <span class="ldr-history-child-count">${esc(String(childCount))} research${childCount !== 1 ? 'es' : ''}</span>
                    <div class="ldr-history-item-status ldr-status-completed">${esc(formatStatus(item.status))}</div>
                </div>
                <div class="ldr-history-item-meta">
                    <div class="ldr-history-item-date">${esc(formattedDate)}</div>
                    <div class="ldr-history-item-mode">${esc(formatMode(item.mode))}</div>
                </div>
                <div class="ldr-history-item-actions">
                    <button class="btn btn-sm ldr-btn-outline ldr-view-btn">
                        <i class="fas fa-comments"></i><span> Open Chat</span>
                    </button>
                    <button class="btn btn-sm ldr-btn-outline ldr-delete-item-btn" title="Delete chat" aria-label="Delete chat">
                        <i class="fas fa-trash-alt"></i>
                    </button>
                </div>
            </div>
            <div class="ldr-history-group-children${isExpanded ? ' ldr-history-group-children--open' : ''}">
            </div>
        `;

        // Populate children
        const childrenContainer = groupEl.querySelector('.ldr-history-group-children');
        if (item._children && item._children.length > 0) {
            item._children.forEach(child => {
                const childEl = createHistoryItemElement(child);
                childEl.classList.add('ldr-history-child-item');
                childrenContainer.appendChild(childEl);
            });
        }

        return groupEl;
    }

    /**
     * Create a semantic-only item element (Tier 3).
     * Tries to find the full history item; if found, renders full card with semantic badge.
     * Otherwise renders a simplified card with View button only.
     */
    function createSemanticOnlyElement(semanticResult) {
        // findItemById walks _children so chat-linked Tier-3 results that
        // live under a parent group still resolve to their full history item.
        // historyItems.find() alone only checks the top level.
        const historyItem = findItemById(semanticResult.research_id);
        const semanticMatch = {
            similarity: semanticResult.similarity,
            snippet: semanticResult.snippet || ''
        };

        if (historyItem) {
            return createHistoryItemElement(historyItem, semanticMatch);
        }

        // Simplified card — no full history data available
        const itemEl = document.createElement('div');
        itemEl.className = 'ldr-history-item ldr-history-item--semantic-only';
        itemEl.dataset.id = semanticResult.research_id;

        const displayTitle = semanticResult.research_title || semanticResult.title || 'Untitled Research';
        let dateStr = '';
        if (semanticResult.research_created_at) {
            try {
                dateStr = new Date(semanticResult.research_created_at).toLocaleDateString();
            } catch {
                dateStr = '';
            }
        }

        // bearer:disable javascript_lang_dangerous_insert_html
        // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: all interpolations use escapeHtml/esc, numeric coercion, or hardcoded strings
        itemEl.innerHTML = `
            <div class="ldr-history-item-header">
                <div class="ldr-history-item-title">${esc(displayTitle)}</div>
            </div>
            <div class="ldr-history-item-ai-match">
                <span class="ldr-ai-match-badge"><i class="fas fa-brain"></i> ${esc(String(semanticResult.similarity))}% match</span>
            </div>
            ${semanticResult.snippet ? `<div class="ldr-history-item-snippet">${renderSnippet(semanticResult.snippet, searchInput ? searchInput.value.trim() : '')}</div>` : ''}
            <div class="ldr-history-item-meta">
                ${dateStr ? `<div class="ldr-history-item-date">${esc(dateStr)}</div>` : ''}
            </div>
            <div class="ldr-history-item-actions">
                <button class="btn btn-sm ldr-btn-outline ldr-view-btn">
                    <i class="fas fa-eye"></i><span> View</span>
                </button>
            </div>
        `;

        return itemEl;
    }

    // Shared tiered merge from semantic_search.js
    const buildTieredResults = (window.SemanticSearch && window.SemanticSearch.buildTieredResults) || function() { return { tier1: [], tier2: [], tier3: [] }; };

    /**
     * Render merged tiered results into the history container.
     */
    function renderMergedResults(tiered) {
        if (!historyContainer) return;

        const { tier1, tier2, tier3 } = tiered;
        const totalCount = tier1.length + tier2.length + tier3.length;

        if (totalCount === 0) {
            historyContainer.innerHTML = `
                <div class="ldr-empty-state">
                    <i class="fas fa-history ldr-empty-icon"></i>
                    <p>No research history found.</p>
                    <p>Try adjusting your search query.</p>
                </div>
            `;
            return;
        }

        // Brief settling transition
        historyContainer.classList.add('ldr-results-settling');

        const fragment = document.createDocumentFragment();

        // Tier 1: both text + semantic
        for (const entry of tier1) {
            fragment.appendChild(createHistoryItemElement(entry.historyItem, entry.semanticMatch));
        }

        // Tier 2: text-only
        for (const entry of tier2) {
            fragment.appendChild(createHistoryItemElement(entry.historyItem));
        }

        // Tier 3: semantic-only (with divider)
        if (tier3.length > 0) {
            const divider = document.createElement('div');
            divider.className = 'ldr-hybrid-divider';
            divider.textContent = 'Also found in content';
            fragment.appendChild(divider);

            for (const entry of tier3) {
                fragment.appendChild(createSemanticOnlyElement(entry.semanticResult));
            }
        }

        historyContainer.innerHTML = '';
        historyContainer.appendChild(fragment);

        // Remove settling class after browser has painted the 0.6 opacity frame
        requestAnimationFrame(() => {
            requestAnimationFrame(() => {
                historyContainer.classList.remove('ldr-results-settling');
            });
        });
    }

    /**
     * Handle subscribe button click
     * @param {Object} item - The research item
     */
    async function handleSubscribe(item) {
        // Redirect to subscription form with pre-filled query
        const params = new URLSearchParams({
            query: item.query,
            name: item.query.substring(0, 50),
            source_id: item.id
        });
        URLValidator.safeAssign(window.location, 'href', `/news/subscriptions/new?${params.toString()}`);
    }

    /**
     * Handle re-run button click
     * Stores research config in sessionStorage and navigates to research page
     * @param {Object} item - The research item to re-run
     */
    function handleRerun(item) {
        if (!item.query) return;
        try {
            const rerunConfig = {
                query: item.query,
                mode: item.mode
            };
            sessionStorage.setItem('rerunConfig', JSON.stringify(rerunConfig));
        } catch (e) {
            SafeLogger.warn('Could not save rerun config:', e);
        }
        URLValidator.safeAssign(window.location, 'href', '/');
    }

    /**
     * Handle copy-query button click. Copies the item's original query
     * to the clipboard so the user can paste it elsewhere (a new
     * research run, an external tool, a bug report) without having to
     * open the research detail page.
     *
     * Directly addresses azrael-229's #4659 comment: "copying query
     * content from failed search in history is somewhat clunky" —
     * previously the only path was open-results → select-all → copy.
     *
     * @param {Object} item - The history item whose query should be copied
     */
    async function handleCopyQuery(item) {
        if (!item || !item.query) return;
        const text = String(item.query);
        let ok;
        try {
            if (navigator.clipboard && window.isSecureContext) {
                await navigator.clipboard.writeText(text);
                ok = true;
            } else {
                // Hidden-textarea fallback for older browsers / insecure
                // contexts (HTTP, iframe without allow="clipboard-write").
                const ta = document.createElement('textarea');
                ta.value = text;
                ta.setAttribute('readonly', '');
                ta.style.position = 'absolute';
                ta.style.left = '-9999px';
                document.body.appendChild(ta);
                ta.select();
                ok = document.execCommand('copy');
                document.body.removeChild(ta);
            }
        } catch (e) {
            SafeLogger.error('Copy-query failed:', e);
            ok = false;
        }
        uiUtils.showMessage(ok ? 'Query copied to clipboard' : 'Copy failed — please copy manually');
    }

    // Modal-based subscription removed - now redirects to dedicated form page

    // Folder loading removed - handled by dedicated form page

    // Subscription creation removed - handled by dedicated form page

    // Subscription status update removed - handled by dedicated form page

    /**
     * Format a title from a query string
     * Truncates long queries and adds ellipsis
     * @param {string} query - The query string
     * @returns {string} Formatted title
     */
    function formatTitleFromQuery(query) {
        if (!query) return 'Untitled Research';

        // Truncate long queries
        if (query.length > 60) {
            return query.substring(0, 57) + '...';
        }

        return query;
    }

    /**
     * Run text filter on historyItems and return the filtered list
     */
    function runTextFilter(searchTerm) {
        const lowerTerm = searchTerm.toLowerCase();
        const result = [];
        historyItems.forEach(item => {
            const titleMatch = item.title ?
                item.title.toLowerCase().includes(lowerTerm) :
                false;
            const queryMatch = item.query ?
                item.query.toLowerCase().includes(lowerTerm) :
                false;
            const parentMatches = titleMatch || queryMatch;

            if (item._is_chat && item._children && item._children.length > 0) {
                if (parentMatches) {
                    // Parent matches: include with all children
                    result.push(item);
                } else {
                    // Check if any children match
                    const matchingChildren = item._children.filter(child => {
                        const cTitle = child.title ? child.title.toLowerCase().includes(lowerTerm) : false;
                        const cQuery = child.query ? child.query.toLowerCase().includes(lowerTerm) : false;
                        return cTitle || cQuery;
                    });
                    if (matchingChildren.length > 0) {
                        // Include parent with only matching children, force expanded
                        const filtered = {
                            ...item,
                            _children: matchingChildren,
                            _forceExpanded: true
                        };
                        result.push(filtered);
                    }
                }
            } else if (parentMatches) {
                result.push(item);
            }
        });
        return result;
    }

    /**
     * Handle search input — text, semantic, or hybrid depending on mode
     */
    function handleSearchInput() {
        if (!searchInput) return;
        // Clean up stale hybrid loading indicator on any mode change/re-entry
        const staleIndicator = document.getElementById('hybrid-loading-indicator');
        if (staleIndicator) staleIndicator.remove();
        const searchTerm = searchInput.value.trim();

        if (!searchTerm) {
            filteredItems = [...historyItems];
            renderHistoryItems();
            return;
        }

        if (searchMode === SM.TEXT) {
            filteredItems = runTextFilter(searchTerm);
            renderHistoryItems();

        } else if (searchMode === SM.SEMANTIC) {
            clearTimeout(semanticDebounceTimer);
            const currentSemanticId = ++semanticSearchId;
            semanticDebounceTimer = setTimeout(async () => {
                if (!window.HistorySearch ||
                    typeof window.HistorySearch.semanticSearchHistory !== 'function' ||
                    typeof window.HistorySearch.renderSemanticResults !== 'function') {
                    if (historyContainer) {
                        historyContainer.innerHTML = `
                            <div class="ldr-empty-state">
                                <i class="fas fa-exclamation-triangle"></i>
                                <p>Semantic search is loading. Please try again.</p>
                            </div>
                        `;
                    }
                    return;
                }
                if (historyContainer) {
                    historyContainer.innerHTML = '<div class="ldr-loading-spinner ldr-centered"><div class="ldr-spinner"></div></div>';
                }
                try {
                    const results = await window.HistorySearch.semanticSearchHistory(searchTerm);
                    if (currentSemanticId !== semanticSearchId) return; // stale
                    if (searchMode !== SM.SEMANTIC) return; // mode changed
                    if (results && results.needsIndexing) {
                        // Race-safe: stale-id guard above ensures only the latest
                        // request reaches this assignment.
                        // eslint-disable-next-line require-atomic-updates
                        historyContainer.innerHTML = `
                            <div class="ldr-empty-state">
                                <i class="fas fa-brain"></i>
                                <p>No research indexed yet. Use the "Index All" button above to enable semantic search.</p>
                            </div>
                        `;
                        return;
                    }
                    window.HistorySearch.renderSemanticResults(results, searchTerm);
                } catch (error) {
                    SafeLogger.error('Semantic search failed:', error);
                    if (historyContainer) {
                        historyContainer.innerHTML = `
                            <div class="ldr-empty-state">
                                <i class="fas fa-exclamation-triangle"></i>
                                <p>Search failed. Please try again.</p>
                            </div>
                        `;
                    }
                }
            }, 500);

        } else if (searchMode === SM.HYBRID) {
            // 1. Instant text filter — render as Tier 2 immediately
            const textResults = runTextFilter(searchTerm);
            filteredItems = textResults;
            renderHistoryItems();

            // 2. Check if semantic search is available
            if (!window.HistorySearch ||
                typeof window.HistorySearch.getSemanticCollectionId !== 'function' ||
                !window.HistorySearch.getSemanticCollectionId()) {
                return; // Not indexed — text results only, no indicator
            }
            if (typeof window.HistorySearch.semanticSearchHistory !== 'function') {
                return;
            }

            // 3. Append loading indicator (remove stale one first)
            const existingIndicator = document.getElementById('hybrid-loading-indicator');
            if (existingIndicator) existingIndicator.remove();
            const loadingDiv = document.createElement('div');
            loadingDiv.className = 'ldr-hybrid-loading';
            loadingDiv.id = 'hybrid-loading-indicator';
            loadingDiv.innerHTML = '<div class="ldr-spinner" style="width: 16px; height: 16px; border-width: 2px;"></div> Searching content...';
            if (historyContainer) historyContainer.appendChild(loadingDiv);

            // 4. Race-condition guard
            const currentSearchId = ++hybridSearchId;

            // 5. Debounced semantic call (separate timer from input debounce)
            clearTimeout(semanticDebounceTimer);
            semanticDebounceTimer = setTimeout(async () => {
                try {
                    const results = await window.HistorySearch.semanticSearchHistory(searchTerm);
                    if (currentSearchId !== hybridSearchId) return; // stale
                    if (searchMode !== SM.HYBRID) {
                        const indicator = document.getElementById('hybrid-loading-indicator');
                        if (indicator) indicator.remove();
                        return;
                    }

                    // Remove loading indicator
                    const indicator = document.getElementById('hybrid-loading-indicator');
                    if (indicator) indicator.remove();

                    if (results && results.needsIndexing) return;

                    // Build tiered merge and re-render
                    const semanticResults = Array.isArray(results) ? results : [];
                    const tiered = buildTieredResults(textResults, semanticResults, { textIdKey: 'id', semanticIdKey: 'research_id' });
                    renderMergedResults(tiered);
                } catch (error) {
                    SafeLogger.error('Hybrid semantic search failed:', error);
                    const indicator = document.getElementById('hybrid-loading-indicator');
                    if (indicator) indicator.remove();
                }
            }, 500);
        }
    }

    /**
     * Handle delete item
     * @param {string} itemId - The item ID to delete
     */
    async function handleDeleteItem(itemId, isChatItem) {
        const item = findItemById(itemId);
        const isChat = isChatItem || (item && item._is_chat);
        const confirmMsg = isChat
            ? 'Are you sure you want to delete this chat session? This action cannot be undone.'
            : 'Are you sure you want to delete this research? This action cannot be undone.';

        if (!confirm(confirmMsg)) {
            return;
        }

        try {
            if (isChat) {
                // Delete chat session via chat API
                const csrfToken = window.api ? window.api.getCsrfToken() : '';
                const response = await fetch(`/api/chat/sessions/${encodeURIComponent(itemId)}`, {
                    method: 'DELETE',
                    headers: {
                        ...(csrfToken ? { 'X-CSRFToken': csrfToken } : {})
                    }
                });
                if (!response.ok) {
                    throw new Error(`API Error: ${response.status} ${response.statusText}`);
                }
            } else {
                // Delete research item via standard API
                await apiUtils.deleteResearch(itemId);
            }

            // Check if the item is a child within a chat group
            const removeChildFromGroup = (items, childId) => {
                for (const parent of items) {
                    if (parent._children) {
                        const idx = parent._children.findIndex(c => String(c.id) === String(childId));
                        if (idx !== -1) {
                            parent._children.splice(idx, 1);
                            return true;
                        }
                    }
                }
                return false;
            };

            if (!isChat) {
                // Try removing from a parent group first
                const removedFromHistory = removeChildFromGroup(historyItems, itemId);
                const removedFromFiltered = removeChildFromGroup(filteredItems, itemId);
                if (!removedFromHistory && !removedFromFiltered) {
                    // Not a child item, remove top-level
                    historyItems = historyItems.filter(it => String(it.id) !== String(itemId));
                    filteredItems = filteredItems.filter(it => String(it.id) !== String(itemId));
                }
            } else {
                // Remove the chat group from top-level arrays
                historyItems = historyItems.filter(it => String(it.id) !== String(itemId));
                filteredItems = filteredItems.filter(it => String(it.id) !== String(itemId));
            }

            // Show success message
            uiUtils.showMessage(isChat ? 'Chat session deleted successfully' : 'Research deleted successfully');

            // Re-render via handleSearchInput to preserve hybrid/semantic state
            handleSearchInput();
        } catch (error) {
            SafeLogger.error('Error deleting item:', error);
            uiUtils.showError('Error deleting item: ' + error.message);
        }
    }

    /**
     * Handle clear history
     */
    async function handleClearHistory() {
        // Warn that this also wipes chat sessions: handleClearHistory deletes
        // every chat session below (not just ResearchHistory rows), so a user
        // who reads only "research history" would lose their chat
        // conversations — and any chat "view research" links — unexpectedly
        // (M_INTEG1).
        if (!confirm('Are you sure you want to clear all research history?\n\nThis also permanently deletes ALL your chat sessions and their conversations. This action cannot be undone.')) {
            return;
        }

        try {
            // Clear research history via API
            await apiUtils.clearResearchHistory();

            // Also delete ALL chat sessions (paginated). Without all:true
            // we only fetched the first 50, so the user could click "Clear
            // All", see success, then reload and find older sessions still
            // there because they were never enumerated for deletion.
            try {
                const chatSessions = await fetchChatSessions({ all: true });
                const csrfToken = window.api ? window.api.getCsrfToken() : '';
                for (const session of chatSessions) {
                    await fetch(`/api/chat/sessions/${encodeURIComponent(session.id)}`, {
                        method: 'DELETE',
                        headers: {
                            ...(csrfToken ? { 'X-CSRFToken': csrfToken } : {})
                        }
                    });
                }
            } catch (chatErr) {
                SafeLogger.warn('Could not clear chat sessions:', chatErr);
            }

            // Clear arrays
            historyItems = [];
            filteredItems = [];

            // Show success message
            uiUtils.showMessage('Research history cleared successfully');

            // Re-render history items
            renderHistoryItems();
        } catch (error) {
            SafeLogger.error('Error clearing history:', error);
            uiUtils.showError('Error clearing history: ' + error.message);
        }
    }

    // Initialize on DOM content loaded
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeHistory);
    } else {
        initializeHistory();
    }
})();
