// News Page JavaScript - Following LDR patterns
// Security: Uses escapeHtml from /static/js/security/xss-protection.js (loaded globally)

// Safe template rendering function
function safeRenderHTML(container, htmlString) {
    // If container is a string (selector), get the element
    if (typeof container === 'string') {
        container = document.querySelector(container);
    }

    if (!container) return;

    // DOMPurify is loaded via app.js as a <script type="module"> in base.html.
    // Module scripts execute after all defer scripts, so DOMPurify may not yet
    // be available when this defer script first runs. Fall back to textContent
    // (which is always XSS-safe) until DOMPurify is ready.
    if (!window.DOMPurify) {
        SafeLogger.warn('DOMPurify not yet loaded — rendering as plain text (safe fallback)');
        while (container.firstChild) {
            container.removeChild(container.firstChild);
        }
        container.textContent = htmlString;
        return;
    }

    const fragment = window.DOMPurify.sanitize(htmlString, { RETURN_DOM_FRAGMENT: true });

    // Clear the container safely
    while (container.firstChild) {
        container.removeChild(container.firstChild);
    }

    container.appendChild(fragment);
}

// Sanitize a URL for safe use in href attributes (blocks javascript:, data:, etc.)
function safeHref(url) {
    if (!url || typeof url !== 'string') return '#';
    const trimmed = url.trim();
    // Allow relative URLs (starting with /)
    if (trimmed.startsWith('/')) return escapeHtml(trimmed);
    // Use URLValidator if available to check for unsafe schemes
    if (typeof URLValidator !== 'undefined' && URLValidator.isSafeUrl) {
        return URLValidator.isSafeUrl(trimmed) ? escapeHtml(trimmed) : '#';
    }
    // Fallback: block known unsafe schemes
    const lower = trimmed.toLowerCase();
    if (lower.startsWith('javascript:') || lower.startsWith('data:') || lower.startsWith('vbscript:')) return '#';
    return escapeHtml(trimmed);
}

// Escape attributes for use in HTML attributes (onclick, etc)
function escapeAttr(unsafe) {
    if (unsafe === null || unsafe === undefined) return '';
    return String(unsafe)
        .replace(/\\/g, "\\\\")
        .replace(/'/g, "\\'")
        .replace(/"/g, '\\"')
        .replace(/\n/g, "\\n")
        .replace(/\r/g, "\\r");
}

// Global state
// No anonymous users allowed - authentication required
const currentUser = null;
let activeSubscription = 'all';
let newsItems = [];
let subscriptions = [];
let lastVisitTime = null;
let seenNewsIds = new Set();
let searchHistory = [];
let autoRefreshInterval = null;
let priorityCheckInterval = null;
let refreshIndicatorInterval = null;
let lastRefreshTime = new Date();
let activeTimeFilter = 'all';
let activeImpactThreshold = 0;
let readNewsIds = new Set();
let expandedNewsIds = new Set();
let savedNewsIds = new Set();

// Semantic search state
const NEWS_SM = { HYBRID: 'hybrid', TEXT: 'text', SEMANTIC: 'semantic' };
let newsSearchMode = NEWS_SM.HYBRID;
let newsCollectionId = null;
let newsSemanticTimer = null;
let newsSearchId = 0;
let newsSemanticMatches = null; // Map: research_id -> { similarity, snippet }

// Optimized news table query template
const getNewsTableQuery = () => `Find UP TO 10 IMPORTANT breaking news stories from TODAY ${new Date().toLocaleDateString()} ONLY.

START YOUR RESPONSE DIRECTLY WITH THE TABLE. NO INTRODUCTION OR PREAMBLE.

CRITICAL: All information MUST be from real, verifiable sources. DO NOT invent or fabricate any news, events, or details. Only report what you find from actual news sources.

OUTPUT FORMAT: Begin immediately with a markdown table using this exact structure:

| SOURCES | DATE | HEADLINE | CATEGORY | WHAT HAPPENED | ANALYSIS | IMPACT |
|---------|------|----------|----------|---------------|----------|--------|
| [Citation numbers] | [YYYY-MM-DD] | [Descriptive headline] | [War/Security/Economy/Disaster/Politics/Tech] | [3 sentences max from actual sources] | [Why it matters + What happens next + Historical context] | [1-10 score] | [Status] |

IMPORTANT: In the SOURCES column, list the citation numbers (e.g., [1, 3, 5]) that support this news item. These should match the numbered references at the end of your report.

Example row:
| [2, 4, 7] | 2025-01-12 | Major Earthquake Strikes Southern California | Disaster | A 7.1 magnitude earthquake hit near Los Angeles causing structural damage. Emergency services report multiple injuries. Aftershocks continue to affect the region. | Major infrastructure test for earthquake preparedness systems. FEMA mobilizing resources. Similar to 1994 Northridge event but with modern building codes. | 9 | Ongoing |

SEARCH STRATEGY:
1. breaking news today major incident developing impact
2. crisis death disaster announcement today hours ago
3. site:reuters.com OR site:bbc.com OR site:cnn.com today
4. geopolitical economic security emergency today

PRIORITIZE BY REAL-WORLD IMPACT:
- Active conflicts with casualties (Impact 8-10)
- Natural disasters affecting thousands (Impact 7-9)
- Economic shocks (>3% market moves) (Impact 6-8)
- Major political shifts (Impact 5-7)
- Critical infrastructure failures (Impact 6-9)

DIVERSITY IS MANDATORY: You MUST find 10 COMPLETELY DIFFERENT events. Examples of what NOT to do:
- ✗ BAD: Multiple stories about the same earthquake (initial report, death toll update, rescue efforts)
- ✗ BAD: Multiple stories about the same political event (announcement, reactions, analysis)
- ✓ GOOD: One earthquake in Japan, one political crisis in Europe, one economic news from US, etc.

If you can only find 7 truly distinct events, show 7. Do NOT pad with duplicate coverage.

REQUIREMENTS:
- Only include stories found from real sources
- Sort by IMPACT score (highest first)
- Each row must be complete with all columns filled
- Keep analysis concise but informative
- Verify sources are from today
- If insufficient real news is found, include fewer rows rather than inventing content

After the table, add:
- **THE BIG PICTURE**: One paragraph connecting today's major events (based on actual findings)
- **WATCH FOR**: 3 bullet points on what to monitor in next 24 hours`;

// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
    SafeLogger.log('News page DOMContentLoaded');
    initializeNewsPage();
});

async function initializeNewsPage() {
    SafeLogger.log('initializeNewsPage called');

    // Load last visit time and seen items
    loadVisitTracking();

    // Load search history
    loadSearchHistory();

    // Load read status
    loadReadStatus();

    // Load saved items
    loadSavedItems();

    // Load expanded state
    loadExpandedState();

    // Set up event listeners first
    setupEventListeners();
    setupSearchModeToggle();

    // Initialize semantic search (non-blocking)
    initNewsSemanticSearch();

    // Load initial data in proper order
    await loadSubscriptions();
    await loadNewsFeed();  // Load main feed first

    // Check if we're coming from a test run
    const activeTestRunResearchId = sessionStorage.getItem('activeTestRunResearchId');
    const activeTestRunQuery = sessionStorage.getItem('activeTestRunQuery');
    if (activeTestRunResearchId) {
        SafeLogger.log('Test run research detected:', activeTestRunResearchId);
        sessionStorage.removeItem('activeTestRunResearchId');
        sessionStorage.removeItem('activeTestRunQuery');

        // Show a message that research is in progress
        showAlert('Your test run is in progress. Results will appear below when ready.', 'info');

        // Start monitoring this specific research with the query
        monitorResearch(activeTestRunResearchId, activeTestRunQuery);
    }

    // Check for active news research
    await checkActiveNewsResearch();

    // Then check for other updates
    checkPriorityStatus();

    // Set up auto-refresh
    priorityCheckInterval = setInterval(checkPriorityStatus, 30000);

    // Update visit time and clear intervals when leaving page
    window.addEventListener('pagehide', cleanupNewsPage);
    window.addEventListener('beforeunload', () => {
        saveVisitTracking();
        saveReadStatus();
    });
}

// Cleanup function to clear all intervals when leaving the page
function cleanupNewsPage() {
    if (priorityCheckInterval) {
        clearInterval(priorityCheckInterval);
        priorityCheckInterval = null;
    }
    if (refreshIndicatorInterval) {
        clearInterval(refreshIndicatorInterval);
        refreshIndicatorInterval = null;
    }
    if (autoRefreshInterval) {
        clearInterval(autoRefreshInterval);
        autoRefreshInterval = null;
    }
    if (newsSemanticTimer) {
        clearTimeout(newsSemanticTimer);
        newsSemanticTimer = null;
    }
}

function setupEventListeners() {
    // Search button
    // Search button still works as a manual trigger
    const searchBtn = document.getElementById('search-btn');
    if (searchBtn) {
        searchBtn.addEventListener('click', handleSearchSubmit);
    }

    // Debounced auto-search on typing (250ms, matching library/history pages)
    let newsInputDebounceTimer = null;
    const newsSearchInput = document.getElementById('news-search');
    if (newsSearchInput) {
        newsSearchInput.addEventListener('input', () => {
            clearTimeout(newsInputDebounceTimer);
            newsInputDebounceTimer = setTimeout(() => handleSearchSubmit(), 250);
        });
    }

    // Create subscription button
    const createSubBtn = document.getElementById('create-subscription-btn');
    if (createSubBtn) {
        SafeLogger.log('Setting up create subscription button event listener');
        createSubBtn.addEventListener('click', () => {
            SafeLogger.log('Create subscription button clicked');
            showNewsSubscriptionModal('', '');
        });
    } else {
        SafeLogger.error('Create subscription button not found!');
    }

    // Run Once button in subscription modal
    const runTemplateBtn = document.getElementById('run-template-btn');
    if (runTemplateBtn) {
        runTemplateBtn.addEventListener('click', async () => {
            const query = document.getElementById('news-subscription-query').value;
            SafeLogger.log('Run Once clicked, query:', query);
            if (query) {
                // Close modal first
                const modalElement = document.getElementById('newsSubscriptionModal');
                if (modalElement) {
                    const modal = bootstrap.Modal.getInstance(modalElement);
                    if (modal) modal.hide();
                }

                // Use the same advanced search function that the search uses
                SafeLogger.log('Calling performAdvancedNewsSearch with query:', query);
                await performAdvancedNewsSearch(query);
            } else {
                SafeLogger.error('No query found in news-subscription-query input');
                showAlert('Please enter a query', 'warning');
            }
        });
    }

    // Search input - Enter key triggers immediately (bypasses debounce)
    const searchInput = document.getElementById('news-search');
    if (searchInput) {
        searchInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                clearTimeout(newsInputDebounceTimer);
                handleSearchSubmit(e);
            }
        });
    }

    // Table view toggle
    const tableToggle = document.getElementById('table-view-toggle');
    if (tableToggle) {
        tableToggle.addEventListener('change', (e) => toggleTableView(e.target.checked));
    }

    // Impact threshold
    const impactSlider = document.getElementById('impact-filter');
    if (impactSlider) {
        impactSlider.addEventListener('input', (e) => {
            const impactValue = document.querySelector('.ldr-impact-value');
            if (impactValue) {
                impactValue.textContent = e.target.value + '+';
            }
            activeImpactThreshold = parseInt(e.target.value, 10);
            renderNewsItems();
        });
    }

    // Event delegation for topic tags in news items
    document.addEventListener('click', (e) => {
        if (e.target.classList.contains('ldr-topic-tag')) {
            const topic = e.target.getAttribute('data-topic');
            if (topic) {
                filterByTopic(topic);
            }
        }
    });

    // Auto-refresh toggle
    const autoRefreshToggle = document.getElementById('auto-refresh');
    if (autoRefreshToggle) {
        autoRefreshToggle.addEventListener('change', (e) => {
            if (e.target.checked) {
                startAutoRefresh();
            } else {
                stopAutoRefresh();
            }
        });

        // Start auto-refresh if enabled by default
        if (autoRefreshToggle.checked) {
            startAutoRefresh();
        }
    }

    // Time filter buttons
    const timeFilters = document.querySelectorAll('.ldr-time-filter-group .ldr-filter-btn');
    timeFilters.forEach(btn => {
        btn.addEventListener('click', (e) => {
            // Remove active class from all buttons
            timeFilters.forEach(b => b.classList.remove('active'));
            // Add active class to clicked button
            e.target.classList.add('active');
            // Set active time filter
            activeTimeFilter = e.target.dataset.time;
            // Re-render news items with filter
            renderNewsItems();
        });
    });

    // Category filters removed - LDR uses dynamic topics instead

    // Event delegation for news card clicks
    document.addEventListener('click', (e) => {
        // Find the closest news-item parent
        const newsItem = e.target.closest('.ldr-news-item');
        if (newsItem) {
            // Check if click was on a button, link, or other interactive element
            const isInteractive = e.target.closest('button, a, .ldr-vote-btn, .ldr-topic-tag, input, textarea');

            if (!isInteractive) {
                // Get the news item ID
                const newsId = newsItem.dataset.newsId;
                const item = newsItems.find(n => n.id === newsId);

                if (item) {
                    // Mark as seen immediately
                    markNewsAsSeen(newsId);
                    saveVisitTracking();

                    // Also mark as read when clicking the card
                    if (!readNewsIds.has(newsId)) {
                        markAsRead(newsId);
                        saveReadStatus();
                    }

                    // Navigate to the full report
                    const url = item.source_url || `/results/${item.research_id}`;
                    if (typeof URLValidator !== 'undefined' && URLValidator.safeAssign) {
                        URLValidator.safeAssign(window.location, 'href', url);
                    } else {
                        // URLValidator not available — fall back to safe internal path only
                        SafeLogger.error('URLValidator not available — blocking external redirect');
                        // server-generated ID in hardcoded /results/ path
                        // bearer:disable javascript_lang_open_redirect
                        window.location.href = `/results/${item.research_id}`;
                    }
                }
            }
        }
    });
}

// Search handling
async function handleSearchSubmit(e) {
    if (e && e.preventDefault) {
        e.preventDefault();
    }
    const query = document.getElementById('news-search').value.trim();

    // If empty query, reset everything
    if (!query) {
        clearSemanticState();
        await loadNewsFeed();
        return;
    }

    // Add to search history only for non-empty queries
    addToSearchHistory(query, 'filter');

    if (newsSearchMode === NEWS_SM.SEMANTIC) {
        // Pure semantic search
        await runNewsSemanticSearch(query);
    } else if (newsSearchMode === NEWS_SM.TEXT) {
        // Text-only: existing behavior
        clearSemanticState();
        await loadNewsFeed(query);
    } else {
        // Hybrid: text results first, then debounced semantic
        clearSemanticState();
        const feedContent = document.getElementById('news-feed-content');
        if (feedContent) feedContent.style.display = '';
        const semanticContainer = document.getElementById('news-semantic-results');
        if (semanticContainer) semanticContainer.style.display = 'none';

        await loadNewsFeed(query);

        // Debounced semantic search
        clearTimeout(newsSemanticTimer);
        newsSemanticTimer = setTimeout(() => {
            runNewsHybridSearch(query);
        }, 500);
    }
}

// Clear search and reload feed
function clearSearch() {
    document.getElementById('news-search').value = '';
    clearSemanticState();
    loadNewsFeed();
}

// Advanced search using LDR search system
async function performAdvancedNewsSearch(query, strategy = 'source-based', modelConfig = null) {
    SafeLogger.log('performAdvancedNewsSearch called with:', { query, strategy, modelConfig });
    showAlert('Performing advanced news analysis...', 'info');

    try {
        // Request will use settings from database if not provided
        // The backend will handle getting the user's configured model
        const requestData = {
            query,
            mode: 'quick',
            strategy,  // Use provided strategy or default to source-based
            metadata: {
                is_news_search: true,
                search_type: 'news_analysis',
                display_in: 'news_feed',
                triggered_by: 'test_run'
            }
        };

        // Add model configuration if provided (for test runs)
        if (modelConfig) {
            if (modelConfig.provider) requestData.model_provider = modelConfig.provider;
            if (modelConfig.model) requestData.model = modelConfig.model;
            if (modelConfig.customEndpoint) requestData.custom_endpoint = modelConfig.customEndpoint;
            if (modelConfig.searchEngine) requestData.search_engine = modelConfig.searchEngine;
            if (modelConfig.iterations) requestData.iterations = modelConfig.iterations;
            if (modelConfig.questions) requestData.questions_per_iteration = modelConfig.questions;
        }

        SafeLogger.log('Sending research request:', requestData);

        const response = await fetch(URLS.API.START_RESEARCH, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCSRFToken()
            },
            body: JSON.stringify(requestData)
        });

        SafeLogger.log('Research API response status:', response.status);

        if (!response.ok) {
            let errorMessage = 'Error starting research';
            try {
                const errorData = await response.json();
                errorMessage = errorData.error || errorData.message || errorMessage;
            } catch {
                // If response is not JSON, use status text
                errorMessage = `${errorMessage}: ${response.statusText}`;
            }

            SafeLogger.error('Research API error:', response.status, errorMessage);

            if (response.status === 401) {
                showAlert('Authentication required. Please log in to perform research.', 'error');
                // Redirect to login after a short delay
                setTimeout(() => {
                    // hardcoded /auth/login target, next param is current page URL
                    // bearer:disable javascript_lang_open_redirect
                    window.location.href = '/auth/login?next=' + encodeURIComponent(window.location.href);
                }, 2000);
                return;
            }

            showAlert(errorMessage, 'error');
            return;
        }

        const data = await response.json();
        SafeLogger.log('Research API response:', data);
        if ((data.status === 'success' || data.status === window.RESEARCH_STATUS.QUEUED) && data.research_id) {
            showAlert('Analyzing news... Results will appear below when ready.', 'info');

            // Show loading state in news feed FIRST
            const container = document.getElementById('news-feed-content');
            // bearer:disable javascript_lang_dangerous_insert_html
            container.innerHTML = `
                <div class="ldr-news-card ldr-priority-high ldr-active-research-card">
                    <div class="ldr-news-header">
                        <h2 class="ldr-news-title">
                            <i class="bi bi-hourglass-split ldr-spinning"></i>
                            Analyzing: "${escapeHtml(query.substring(0, 60))}..."
                        </h2>
                    </div>
                    <div class="ldr-news-meta">
                        <span><i class="bi bi-info-circle"></i> Research ID: ${escapeHtml(data.research_id)}</span>
                        <span><i class="bi bi-clock"></i> Started just now</span>
                    </div>
                    <div class="ldr-news-summary">
                        <div class="progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="10" aria-label="Research progress">
                            <div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 10%"></div>
                        </div>
                        <p class="mt-2">Searching for breaking news and analyzing importance...</p>
                    </div>
                </div>
            `;

            // Poll for results
            pollForNewsResearchResults(data.research_id, query);

            // Try to create subscription but don't let it block the loading
            createSubscriptionFromSearch(query, data.research_id).catch(err => {
                SafeLogger.error('Failed to create subscription:', err);
            });
        } else {
            SafeLogger.error('Unexpected response format:', data);
            showAlert('Failed to start research - unexpected response', 'error');
        }
    } catch (error) {
        SafeLogger.error('Error in advanced search:', error);
        showAlert('Error performing search', 'error');
    }
}

// Create subscription from search
async function createSubscriptionFromSearch(query, researchId) {
    try {
        const response = await fetch('/news/api/subscribe', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCSRFToken()
            },
            body: JSON.stringify({
                query,
                subscription_type: 'search',
                refresh_minutes: 60,
                metadata: {
                    research_id: researchId,
                    is_advanced_query: true
                }
            })
        });

        if (response.ok) {
            await loadSubscriptions();
            showAlert('Advanced news subscription created!', 'success');
        }
    } catch (error) {
        SafeLogger.error('Error creating subscription:', error);
    }
}

// Create subscription from news item
function createSubscriptionFromItem(newsId) {
    const item = newsItems.find(n => n.id === newsId);
    if (!item) {
        showAlert('News item not found', 'error');
        return;
    }

    // Navigate to subscription form with pre-filled query
    const params = new URLSearchParams({
        query: item.query || item.headline,
        name: `Subscription: ${item.headline.substring(0, 50)}...`,
        research_id: item.research_id
    });

    // hardcoded /news path, only query params are dynamic
    // bearer:disable javascript_lang_open_redirect
    window.location.href = `/news/subscriptions/new?${params.toString()}`;
}

// Display advanced results
function displayAdvancedResults(data) {
    const content = data.formatted_response || '';

    // Check for table content
    if (content.includes('<table>') || content.includes('|')) {
        document.getElementById('table-view-toggle').checked = true;
        toggleTableView(true);
        parseAndDisplayTable(content);
    } else {
        showAlert('Results loaded. Check your feed!', 'success');
        loadNewsFeed();
    }
}

// Parse and display news table
function parseAndDisplayTable(content) {
    const tableBody = document.getElementById('news-table-body');
    const rows = [];

    // Parse markdown table
    const lines = content.split('\n');
    let inTable = false;

    for (const line of lines) {
        // Check for table header - now includes SOURCES as first column
        if ((line.includes('SOURCES') || line.includes('SOURCE')) && line.includes('HEADLINE')) {
            inTable = true;
            continue;
        }
        // Skip separator lines
        if (line.includes('---') && line.includes('|')) {
            continue;
        }
        if (inTable && line.includes('|')) {
            const cells = line.split('|').map(cell => cell.trim()).filter(cell => cell);
            if (cells.length >= 5) {  // At least SOURCES, DATE/TIME, HEADLINE, etc.
                rows.push(cells);
            }
        }
    }

    // Render rows with SOURCES as first column
    if (rows.length > 0) {
        safeRenderHTML(tableBody, rows.map(row => {
            // Check if we have an impact score column (usually second to last)
            const impactIndex = row.length >= 7 ? 6 : (row.length >= 6 ? 5 : -1);
            const hasImpact = impactIndex > 0 && !isNaN(parseInt(row[impactIndex], 10));

            return `
            <tr>
                <td>${row[0] || ''}</td>
                <td class="text-nowrap">${row[1] || ''}</td>
                <td><strong>${row[2] || ''}</strong></td>
                <td>${row[3] || ''}</td>
                <td>${row[4] || ''}</td>
                <td>${row[5] || ''}</td>
                ${hasImpact ? `
                    <td>
                        <span class="ldr-impact-badge ${getImpactClass(row[impactIndex])}">
                            ${row[impactIndex] || 'N/A'}
                        </span>
                    </td>
                ` : row[6] ? `
                    <td>${row[6] || ''}</td>
                ` : ''}
            </tr>
        `}).join(''));
    }
}

// Simple subscription
async function createSimpleSubscription(query) {
    showAlert(`Creating subscription for: ${query}`, 'info');

    try {
        const response = await fetch('/news/api/subscribe', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCSRFToken()
            },
            body: JSON.stringify({
                query,
                subscription_type: 'search',
                refresh_minutes: 1
            })
        });

        if (response.ok) {
            await loadSubscriptions();
            await loadNewsFeed();
            showAlert('Subscription created!', 'success');
            document.getElementById('news-query').value = '';
        } else {
            showAlert('Failed to create subscription', 'error');
        }
    } catch (error) {
        SafeLogger.error('Error creating subscription:', error);
        showAlert('Error creating subscription', 'error');
    }
}

// Load subscriptions
async function loadSubscriptions() {
    try {
        const response = await fetch('/news/api/subscriptions/current', {
            credentials: 'same-origin'
        });
        if (response.ok) {
            const data = await response.json();
            subscriptions = data.subscriptions || [];
            // renderSubscriptions(); // Removed sidebar subscription list
        }
    } catch (error) {
        SafeLogger.error('Error loading subscriptions:', error);
    }
}

// Render subscriptions - REMOVED: Subscription list moved to dedicated page
/* function renderSubscriptions() {
    const container = document.getElementById('sidebar-subscriptions-list');

    // Add "All" option
    let html = `
        <div class="ldr-subscription-item ${activeSubscription === 'all' ? 'active' : ''}"
             onclick="selectSubscription('all')">
            <div class="ldr-subscription-type">ALL FEEDS</div>
            <div class="ldr-subscription-query">Combined News Feed</div>
            <div class="ldr-subscription-meta">All your subscriptions</div>
        </div>
        <div class="ldr-subscription-item ${activeSubscription === 'saved' ? 'active' : ''}"
             onclick="selectSubscription('saved')">
            <div class="ldr-subscription-type">SAVED</div>
            <div class="ldr-subscription-query">Bookmarked Items</div>
            <div class="ldr-subscription-meta">${savedNewsIds.size} saved items</div>
        </div>
    `;

    // Add user subscriptions
    subscriptions.forEach(sub => {
        const typeLabel = sub.type === 'search' ? 'SEARCH' : 'TOPIC';
        const query = sub.query || sub.topic || 'Unknown';
        const nextRefresh = sub.next_refresh ? new Date(sub.next_refresh).toLocaleString() : 'Soon';

        html += `
            <div class="ldr-subscription-item ${activeSubscription === sub.id ? 'active' : ''}"
                 onclick="selectSubscription('${sub.id}')">
                <div class="ldr-subscription-header">
                    <div>
                        <div class="ldr-subscription-type">${typeLabel}</div>
                        <div class="ldr-subscription-query">${query}</div>
                        <div class="ldr-subscription-meta">Next: ${nextRefresh}</div>
                    </div>
                    <button class="btn btn-sm ldr-btn-ghost" onclick="showSubscriptionHistory('${sub.id}'); event.stopPropagation();" title="View history">
                        <i class="bi bi-clock-history"></i>
                    </button>
                </div>
            </div>
        `;
    });

    safeRenderHTML(container, html);
} */

// Select subscription
function selectSubscription(subId) {
    activeSubscription = subId;
    // renderSubscriptions(); // Removed sidebar subscription list
    clearSemanticState();
    loadNewsFeed();
}

// Load news feed
async function loadNewsFeed(focus = null) {
    // Clear stale semantic badges — hybrid search will re-set them if needed
    newsSemanticMatches = null;

    const container = document.getElementById('news-feed-content');
    if (!container) {
        SafeLogger.error('News container not found!');
        return;
    }
    safeRenderHTML(container, '<div class="ldr-loading-placeholder"><div class="ldr-loading-spinner"></div><p>Loading news...</p></div>');

    // Update feed header if subscription is selected
    updateFeedHeader();

    // Handle saved items view
    if (activeSubscription === 'saved') {
        loadSavedNewsFeed();
        return;
    }

    try {
        const params = new URLSearchParams({
            limit: 20,
            use_cache: true
        });

        if (focus) params.append('focus', focus);
        if (activeSubscription !== 'all') params.append('subscription_id', activeSubscription);

        SafeLogger.log('Fetching news from:', `/news/api/feed?${params}`);
        const response = await fetch(`/news/api/feed?${params}`);
        SafeLogger.log('Response status:', response.status);

        if (response.ok) {
            const data = await response.json();
            newsItems = data.news_items || [];
            SafeLogger.log(`Loaded ${newsItems.length} news items from API`);
            SafeLogger.log('API response:', data);
            if (newsItems.length > 0) {
                SafeLogger.log('First news item:', newsItems[0]);
            }

            // Apply client-side filtering if focus is provided
            if (focus) {
                const searchTerm = focus.toLowerCase();
                const filteredItems = newsItems.filter(item => {
                    const headline = (item.headline || '').toLowerCase();
                    const summary = (item.summary || '').toLowerCase();
                    const query = (item.query || '').toLowerCase();
                    const topics = (item.topics || []).map(t => t.toLowerCase()).join(' ');

                    return headline.includes(searchTerm) ||
                           summary.includes(searchTerm) ||
                           query.includes(searchTerm) ||
                           topics.includes(searchTerm);
                });
                SafeLogger.log(`Filtered to ${filteredItems.length} items matching "${focus}"`);

                // Update newsItems with filtered results (even if empty)
                newsItems = filteredItems;
            }

            renderNewsItems(focus);
            extractTrendingTopics();
            updateBulkActionsBar();

            // Load existing votes for all news items
            await loadVotesForNewsItems();
        } else {
            SafeLogger.error('Failed to load news feed. Status:', response.status);
            const errorText = await response.text();
            SafeLogger.error('Error response:', errorText);
            safeRenderHTML(container, `<p class="ldr-error-message">Failed to load news feed (${response.status})</p>`);
        }
    } catch (error) {
        SafeLogger.error('Error loading news:', error);
        safeRenderHTML(container, '<p class="ldr-error-message">Error loading news feed</p>');
    }
}

// Render news items
function renderNewsItems(searchQuery = null) {
    const container = document.getElementById('news-feed-content');

    SafeLogger.log('renderNewsItems called with', newsItems.length, 'items');

    // Apply all filters
    let itemsToRender = newsItems;

    // Apply topic filter if active
    if (activeTopicFilter) {
        itemsToRender = itemsToRender.filter(item => {
            return item.topics && item.topics.includes(activeTopicFilter);
        });
    }

    // Apply time filter
    if (activeTimeFilter && activeTimeFilter !== 'all') {
        itemsToRender = itemsToRender.filter(item => {
            return isWithinTimeFilter(item, activeTimeFilter);
        });
    }

    // Category filter removed - using dynamic topics instead

    // Apply impact filter
    if (activeImpactThreshold > 0) {
        itemsToRender = itemsToRender.filter(item => {
            return item.impact_score >= activeImpactThreshold;
        });
    }

    // Preserve any existing filter bar
    const existingFilterBar = container.querySelector('.ldr-active-filter-bar');

    // Debug logging removed - was causing console spam

    if (itemsToRender.length === 0) {
        // Build filter status message
        const filterMessage = [];
        if (searchQuery) filterMessage.push(`search: "${escapeHtml(searchQuery)}"`);
        if (activeTopicFilter) filterMessage.push(`topic: ${escapeHtml(activeTopicFilter)}`);
        if (activeTimeFilter !== 'all') filterMessage.push(`time: ${activeTimeFilter}`);
        if (activeImpactThreshold > 0) filterMessage.push(`impact ≥ ${activeImpactThreshold}`);

        const hasFilters = filterMessage.length > 0;
        const filterText = hasFilters ? ` matching ${filterMessage.join(', ')}` : '';

        // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: all interpolations use escapeHtml/esc, numeric coercion, or hardcoded strings
        container.innerHTML = `
            <div class="ldr-empty-state">
                <i class="fas fa-newspaper"></i>
                <h3>No news items${filterText}</h3>
                <p>${hasFilters ? 'Try adjusting your filters or' : 'Start by searching for topics or creating subscriptions'}</p>
                ${hasFilters ? `
                    <button class="btn btn-sm btn-outline-secondary mt-2" onclick="clearAllFilters()">
                        <i class="bi bi-x-circle"></i> Clear all filters
                    </button>
                ` : ''}
            </div>
        `;
        // Re-add filter bar if it existed
        if (existingFilterBar && activeTopicFilter) {
            container.insertBefore(existingFilterBar, container.firstChild);
        }
        return;
    }

    // Add search indicator if searching
    let searchIndicatorHtml = '';
    if (searchQuery) {
        searchIndicatorHtml = `
            <div class="ldr-search-indicator alert alert-info">
                <i class="bi bi-search"></i> Showing results for: <strong>"${escapeHtml(searchQuery)}"</strong>
                <button class="btn btn-sm btn-link" onclick="clearSearch()">
                    <i class="bi bi-x-circle"></i> Clear search
                </button>
            </div>
        `;
    }

    const fullHtml = searchIndicatorHtml + itemsToRender.map(item => {
        const priorityClass = getPriorityClass(item.impact_score);
        const isNew = isNewsNew(item);
        const isRead = readNewsIds.has(item.id);
        const newClasses = isNew ? 'ldr-is-new ldr-fade-in-new' : '';
        const readClasses = isRead ? 'ldr-is-read' : 'ldr-is-unread';

        // Add to seen items if rendering
        if (!seenNewsIds.has(item.id)) {
            // Mark as seen after a delay
            setTimeout(() => markNewsAsSeen(item.id), 3000);
        }

        // Render findings or semantic snippet as preview
        let findingsHtml = '';
        const semanticMatch = newsSemanticMatches && newsSemanticMatches.get(String(item.research_id));
        if (semanticMatch && semanticMatch.snippet) {
            // Show semantic snippet — the relevant chunk that matched the query
            const renderSnippet = window.SemanticSearch && window.SemanticSearch.renderSnippet;
            const currentQuery = searchQuery || (document.getElementById('news-search') ? document.getElementById('news-search').value.trim() : '');
            const snippetContent = renderSnippet
                ? renderSnippet(semanticMatch.snippet, currentQuery)
                : escapeHtml(semanticMatch.snippet);
            findingsHtml = `<div class="ldr-news-findings ldr-semantic-snippet">
                <small class="text-muted"><i class="fas fa-brain" aria-hidden="true"></i> Matched content:</small>
                <div>${snippetContent}</div>
            </div>`;
        } else if (item.findings) {
            // Limit findings to first 800 characters for preview
            const findingsPreview = item.findings.substring(0, 800) + (item.findings.length > 800 ? '...' : '');

            // Check if we have the renderMarkdown function available
            if (typeof window.ui !== 'undefined' && window.ui.renderMarkdown) {
                findingsHtml = `<div class="ldr-news-findings">${window.ui.renderMarkdown(findingsPreview)}</div>`;
            } else if (typeof renderMarkdown !== 'undefined') {
                findingsHtml = `<div class="ldr-news-findings">${renderMarkdown(findingsPreview)}</div>`;
            } else {
                // Basic fallback rendering — escape first to prevent XSS
                const escaped = (window.escapeHtml || String)(findingsPreview);
                const basicHtml = escaped
                    .replace(/\n\n/g, '</p><p>')
                    .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
                    .replace(/\*(.*?)\*/g, '<em>$1</em>')
                    .replace(/^- (.+)$/gm, '<li>$1</li>')
                    .replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
                findingsHtml = `<div class="ldr-news-findings"><p>${basicHtml}</p></div>`;
            }
        } else if (item.summary) {
            // Fallback to summary if no findings
            findingsHtml = `<div class="ldr-news-summary">${escapeHtml(item.summary)}</div>`;
        }

        // Render topics as clickable tags
        let topicsHtml = '';
        if (item.topics && item.topics.length > 0) {
            const topicTags = item.topics.map(topic =>
                `<span class="ldr-topic-tag" data-topic="${escapeHtml(topic)}" title="Click to filter by ${escapeHtml(topic)}">${escapeHtml(topic)}</span>`
            ).join('');
            topicsHtml = `<div class="ldr-news-topics">${topicTags}</div>`;
        }
        const isExpanded = expandedNewsIds.has(item.id);

        return `
            <div class="ldr-news-item ldr-priority-${priorityClass} ${newClasses} ${readClasses} ldr-is-expanded" data-news-id="${escapeHtml(item.id)}">
                ${isNew ? '<span class="ldr-new-indicator">New</span>' : ''}
                <div class="ldr-news-item-header">
                    <div class="ldr-news-headline">
                        ${escapeHtml(item.headline)}
                        ${newsSemanticMatches && newsSemanticMatches.has(String(item.research_id))
                            ? `<span class="ldr-ai-match-badge"><i class="fas fa-brain"></i> ${Math.round(newsSemanticMatches.get(String(item.research_id)).similarity || 0)}% match</span>`
                            : ''}
                    </div>
                    <div class="ldr-news-actions-menu">
                        <button class="btn btn-sm ldr-btn-ghost" onclick="toggleReadStatus('${escapeAttr(item.id)}')" title="${isRead ? 'Mark as unread' : 'Mark as read'}">
                            <i class="bi ${isRead ? 'bi-envelope-open' : 'bi-envelope'}"></i>
                        </button>
                        <div class="dropdown">
                            <button class="btn btn-sm ldr-btn-ghost" data-bs-toggle="dropdown" title="More actions">
                                <i class="bi bi-three-dots-vertical"></i>
                            </button>
                            <ul class="dropdown-menu dropdown-menu-end">
                                <li><a class="dropdown-item" href="#" onclick="shareNews('${escapeAttr(item.id)}'); return false;">
                                    <i class="bi bi-share"></i> Share
                                </a></li>
                                <li><a class="dropdown-item" href="#" onclick="copyNewsLink('${escapeAttr(item.id)}'); return false;">
                                    <i class="bi bi-link-45deg"></i> Copy Link
                                </a></li>
                                <li><a class="dropdown-item" href="#" onclick="exportToMarkdown('${escapeAttr(item.id)}'); return false;">
                                    <i class="bi bi-markdown"></i> Export as Markdown
                                </a></li>
                                <li><hr class="dropdown-divider"></li>
                                <li><a class="dropdown-item" href="#" onclick="hideNewsItem('${escapeAttr(item.id)}'); return false;">
                                    <i class="bi bi-eye-slash"></i> Hide this item
                                </a></li>
                            </ul>
                        </div>
                    </div>
                </div>
                <div class="ldr-news-meta">
                    <span class="ldr-news-category">${escapeHtml(item.category || 'General')}</span>
                    <span class="ldr-impact-indicator">
                        Impact:
                        <div class="ldr-impact-bar">
                            <div class="ldr-impact-fill" style="width: ${Math.max(0, Math.min(100, (Number(item.impact_score) || 0) * 10))}%"></div>
                        </div>
                        ${Number(item.impact_score) || 0}/10
                    </span>
                    <span><i class="fas fa-calendar"></i> ${escapeHtml(formatNewsDate(item.created_at) || item.time_ago || 'Recently')}</span>
                </div>
                ${findingsHtml}
                ${topicsHtml}
                ${renderSourceLinks(item.links)}
                <div class="ldr-news-actions">
                    <div class="ldr-vote-buttons">
                        <button class="ldr-vote-btn" onclick="vote('${escapeAttr(item.id)}', 'up')">
                            <i class="fas fa-thumbs-up"></i> ${Number(item.upvotes) || 0}
                        </button>
                        <button class="ldr-vote-btn" onclick="vote('${escapeAttr(item.id)}', 'down')">
                            <i class="fas fa-thumbs-down"></i> ${Number(item.downvotes) || 0}
                        </button>
                    </div>
                    <div class="ldr-action-buttons">
                        <a href="${safeHref(item.source_url || `/results/${item.research_id}`)}" class="btn btn-primary btn-sm" onclick="markAsReadOnClick('${escapeAttr(item.id)}')">
                            <i class="fas fa-file-alt"></i> View Full Report
                        </a>
                        <button class="btn btn-secondary btn-sm ldr-save-btn" onclick="toggleSaveItem('${escapeAttr(item.id)}')" title="${savedNewsIds.has(item.id) ? 'Remove from saved' : 'Save for later'}">
                            <i class="${savedNewsIds.has(item.id) ? 'bi bi-bookmark-fill' : 'bi bi-bookmark'}"></i>
                        </button>
                        ${item.query ? `<button class="btn btn-outline-primary btn-sm" onclick="createSubscriptionFromItem('${escapeAttr(item.id)}')" title="Create subscription from this search">
                            <i class="bi bi-bell-plus"></i> Subscribe
                        </button>` : ''}
                    </div>
                </div>
            </div>
        `;
    }).join('');

    // Use safe rendering
    safeRenderHTML(container, fullHtml);

    // Update table view if active
    const tableToggle = document.getElementById('table-view-toggle');
    if (tableToggle && tableToggle.checked) {
        populateNewsTable();
    }
}

// Populate news table
function populateNewsTable() {
    const tableBody = document.getElementById('news-table-body');

    if (!tableBody) {
        SafeLogger.error('Table body not found');
        return;
    }

    if (newsItems.length === 0) {
        safeRenderHTML(tableBody, '<tr><td colspan="7" style="text-align: center;">No news items to display</td></tr>');
        return;
    }

    safeRenderHTML(tableBody, newsItems.map(item => {
        // Extract or format date/time
        const dateTime = formatNewsDateTime(item.created_at || item.time_ago);

        const impactClass = getImpactClass(item.impact_score);
        const summary = item.summary || '';
        const [whatHappened, ...analysisLines] = summary.split('. ');
        const analysis = analysisLines.join('. ');
        const sources = item.sources || [item.source_url];
        // Validate and escape source URLs
        const sourceLinks = sources.filter(src => src).map((src, idx) => {
            // Validate URL is http/https
            try {
                const url = new URL(src);
                if (url.protocol !== 'http:' && url.protocol !== 'https:') return '';
                return `<a href="${escapeHtml(src)}" target="_blank" rel="noopener noreferrer">[${idx + 1}]</a>`;
            } catch {
                return '';
            }
        }).join(' ');

        return `
            <tr>
                <td>${sourceLinks}</td>
                <td class="text-nowrap">${escapeHtml(dateTime)}</td>
                <td><strong>${escapeHtml(item.headline)}</strong></td>
                <td>${escapeHtml(item.category || 'General')}</td>
                <td>${escapeHtml(whatHappened || 'Details pending...')}</td>
                <td>${escapeHtml(analysis || 'Analysis in progress...')}</td>
                <td>
                    <span class="ldr-impact-badge ${impactClass}">
                        ${Number(item.impact_score) || 0}/10
                    </span>
                </td>
            </tr>
        `;
    }).join(''));
}

// Format date/time for news items
function formatNewsDateTime(dateStr) {
    if (!dateStr) return new Date().toLocaleString('en-US', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });

    // If it's already a formatted time like "2 hours ago"
    if (dateStr.includes('ago') || dateStr.includes('Just now')) {
        return dateStr;
    }

    // Otherwise parse and format the date
    try {
        const date = new Date(dateStr);
        return date.toLocaleString('en-US', {
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit',
            hour12: false
        });
    } catch {
        return dateStr;
    }
}

// Format date for news items in ISO format with time
function formatNewsDate(dateStr) {
    if (!dateStr) return null;

    try {
        const date = new Date(dateStr);
        // Return ISO date and time format YYYY-MM-DD HH:MM
        const isoDate = date.toISOString().split('T')[0];
        const hours = String(date.getUTCHours()).padStart(2, '0');
        const minutes = String(date.getUTCMinutes()).padStart(2, '0');
        return `${isoDate} ${hours}:${minutes}`;
    } catch {
        return null;
    }
}

// Helper functions
function getPriorityClass(score) {
    if (score >= 7) return 'high';
    if (score >= 4) return 'medium';
    return 'low';
}

function getImpactClass(score) {
    const num = parseInt(score, 10);
    if (num >= 7) return 'impact-high';
    if (num >= 4) return 'impact-medium';
    return 'impact-low';
}

// Load existing votes for all displayed news items
async function loadVotesForNewsItems() {
    if (!newsItems || newsItems.length === 0) return;

    try {
        const cardIds = newsItems.map(item => item.id);
        const response = await fetch('/news/api/feedback/batch', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCSRFToken()
            },
            body: JSON.stringify({
                card_ids: cardIds
            })
        });

        if (response.ok) {
            const data = await response.json();
            if (data.votes) {
                // Update UI with vote counts and user's votes
                for (const [cardId, voteInfo] of Object.entries(data.votes)) {
                    const item = document.querySelector(`[data-news-id="${cardId}"]`);
                    if (item) {
                        const upBtn = item.querySelector('.ldr-vote-btn:first-child');
                        const downBtn = item.querySelector('.ldr-vote-btn:last-child');

                        if (upBtn && downBtn) {
                            // Update vote counts (ensure numeric values to prevent XSS)
                            // 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
                            upBtn.innerHTML = `<i class="fas fa-thumbs-up"></i> ${Number(voteInfo.upvotes) || 0}`;
                            // 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
                            downBtn.innerHTML = `<i class="fas fa-thumbs-down"></i> ${Number(voteInfo.downvotes) || 0}`;

                            // Show user's existing vote
                            if (voteInfo.user_vote === 'up') {
                                upBtn.classList.add('ldr-voted');
                                downBtn.classList.remove('ldr-voted');
                            } else if (voteInfo.user_vote === 'down') {
                                downBtn.classList.add('ldr-voted');
                                upBtn.classList.remove('ldr-voted');
                            } else {
                                upBtn.classList.remove('ldr-voted');
                                downBtn.classList.remove('ldr-voted');
                            }
                        }
                    }
                }
            }
        }
    } catch (error) {
        SafeLogger.error('Error loading votes:', error);
    }
}

// Vote on news item
async function vote(newsId, voteType) {
    try {
        const response = await fetch(`/news/api/feedback/${newsId}`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCSRFToken()
            },
            body: JSON.stringify({
                vote: voteType
            })
        });

        if (response.ok) {
            const data = await response.json();
            // Update UI
            const item = document.querySelector(`[data-news-id="${newsId}"]`);
            if (item) {
                const upBtn = item.querySelector('.ldr-vote-btn:first-child');
                const downBtn = item.querySelector('.ldr-vote-btn:last-child');
                // Ensure numeric values to prevent XSS
                // 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
                upBtn.innerHTML = `<i class="fas fa-thumbs-up"></i> ${Number(data.upvotes) || 0}`;
                // 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
                downBtn.innerHTML = `<i class="fas fa-thumbs-down"></i> ${Number(data.downvotes) || 0}`;

                if (voteType === 'up') {
                    upBtn.classList.add('ldr-voted');
                    downBtn.classList.remove('ldr-voted');
                } else {
                    downBtn.classList.add('ldr-voted');
                    upBtn.classList.remove('ldr-voted');
                }
            }
        }
    } catch (error) {
        SafeLogger.error('Error voting:', error);
    }
}

// Save/bookmark functionality
// savedNewsIds is already declared at the top of the file

function loadSavedItems() {
    const saved = localStorage.getItem('saved_news_ids');
    if (saved) {
        try {
            const ids = JSON.parse(saved);
            savedNewsIds = new Set(ids);
        } catch {
            savedNewsIds = new Set();
        }
    }
}

function saveSavedItems() {
    localStorage.setItem('saved_news_ids', JSON.stringify(Array.from(savedNewsIds)));
}

function loadExpandedState() {
    const saved = localStorage.getItem('expanded_news_ids');
    if (saved) {
        try {
            const ids = JSON.parse(saved);
            expandedNewsIds = new Set(ids);
        } catch {
            expandedNewsIds = new Set();
        }
    }
}

function saveItem(newsId) {
    const item = newsItems.find(n => n.id === newsId);
    if (item) {
        // Store full item data
        const savedItems = JSON.parse(localStorage.getItem('saved_news_items') || '{}');
        savedItems[newsId] = {
            ...item,
            savedAt: new Date().toISOString()
        };
        localStorage.setItem('saved_news_items', JSON.stringify(savedItems));

        // Track saved ID
        savedNewsIds.add(newsId);
        saveSavedItems();

        // Update UI
        updateSaveButton(newsId, true);
        showAlert('Item saved for later', 'success');
    }
}

function unsaveItem(newsId) {
    savedNewsIds.delete(newsId);
    saveSavedItems();

    // Remove from full data
    const savedItems = JSON.parse(localStorage.getItem('saved_news_items') || '{}');
    delete savedItems[newsId];
    localStorage.setItem('saved_news_items', JSON.stringify(savedItems));

    // Update UI
    updateSaveButton(newsId, false);
    showAlert('Item removed from saved', 'info');
}

function toggleSaveItem(newsId) {
    if (savedNewsIds.has(newsId)) {
        unsaveItem(newsId);
    } else {
        saveItem(newsId);
    }
}

function updateSaveButton(newsId, isSaved) {
    const btn = document.querySelector(`[data-news-id="${newsId}"] .ldr-save-btn`);
    if (btn) {
        const icon = btn.querySelector('i');
        if (icon) {
            icon.className = isSaved ? 'bi bi-bookmark-fill' : 'bi bi-bookmark';
        }
    }
}

// Load saved news feed
function loadSavedNewsFeed() {
    const savedItems = JSON.parse(localStorage.getItem('saved_news_items') || '{}');
    const savedArray = Object.values(savedItems).sort((a, b) =>
        new Date(b.savedAt) - new Date(a.savedAt)
    );

    // Set newsItems to saved items
    newsItems = savedArray;

    // Render items
    renderNewsItems();
    extractTrendingTopics();
    updateBulkActionsBar();

    // Show special header for saved items
    const container = document.getElementById('news-feed-content');
    if (savedArray.length === 0) {
        container.innerHTML = `
            <div class="ldr-empty-state">
                <i class="bi bi-bookmark"></i>
                <h3>No saved items</h3>
                <p>Save news items to read them later</p>
            </div>
        `;
    }
}

// Toggle expanded state for news items
function toggleExpanded(newsId) {
    if (expandedNewsIds.has(newsId)) {
        expandedNewsIds.delete(newsId);
    } else {
        expandedNewsIds.add(newsId);
    }

    // Update the specific news item's appearance
    const newsItem = document.querySelector(`[data-news-id="${newsId}"]`);
    if (newsItem) {
        newsItem.classList.toggle('ldr-is-expanded');

        // Update button text and icon
        const toggleBtn = newsItem.querySelector('.ldr-expand-toggle-btn');
        if (toggleBtn) {
            const isExpanded = expandedNewsIds.has(newsId);
            // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: all interpolations use escapeHtml/esc, numeric coercion, or hardcoded strings
            toggleBtn.innerHTML = `
                <i class="bi bi-chevron-${isExpanded ? 'up' : 'down'}"></i>
                ${isExpanded ? 'Show less' : 'Show more'}
            `;
            toggleBtn.title = isExpanded ? 'Show less' : 'Show more';
        }

        // Update findings display
        const findings = newsItem.querySelector('.ldr-news-findings');
        if (findings) {
            if (expandedNewsIds.has(newsId)) {
                findings.style.maxHeight = 'none';
            } else {
                findings.style.maxHeight = '400px';
            }
        }
    }

    // Save state to localStorage
    localStorage.setItem('expanded_news_ids', JSON.stringify(Array.from(expandedNewsIds)));
}

// Toggle table view
function toggleTableView(showTable) {
    const cardView = document.getElementById('news-feed-content');
    let tableView = document.getElementById('news-table-view');

    // Create table view if it doesn't exist
    if (!tableView && showTable) {
        tableView = createTableViewHTML();
        cardView.parentNode.insertBefore(tableView, cardView.nextSibling);
    }

    if (showTable && tableView) {
        cardView.style.display = 'none';
        tableView.style.display = 'block';
        populateNewsTable();
    } else {
        cardView.style.display = 'block';
        if (tableView) tableView.style.display = 'none';
    }
}

// Create table view HTML structure
function createTableViewHTML() {
    const tableContainer = document.createElement('div');
    tableContainer.id = 'news-table-view';
    tableContainer.className = 'ldr-news-table-container';
    tableContainer.style.display = 'none';
    tableContainer.innerHTML = `
        <table class="ldr-news-table">
            <thead>
                <tr>
                    <th>SOURCES</th>
                    <th>DATE/TIME</th>
                    <th>HEADLINE</th>
                    <th>CATEGORY</th>
                    <th>WHAT HAPPENED</th>
                    <th>ANALYSIS</th>
                    <th>IMPACT</th>
                </tr>
            </thead>
            <tbody id="news-table-body">
            </tbody>
        </table>
    `;
    return tableContainer;
}

// Filter by impact
function filterNewsByImpact(threshold) {
    activeImpactThreshold = parseInt(threshold, 10);
    updateFilterStatusBar();
    renderNewsItems();
}

// Check priority status
async function checkPriorityStatus() {
    try {
        // Check history for any active research
        const response = await fetch('/api/history');
        if (response.ok) {
            const data = await response.json();
            // Filter out news searches from active research display
            const activeResearch = data.items?.filter(item =>
                ResearchStates.isActive(item.status) &&
                !item.metadata?.is_news_search
            ) || [];

            const statusDiv = document.getElementById('priority-status');
            const message = document.getElementById('priority-message');

            if (activeResearch.length > 0) {
                statusDiv.style.display = 'block';
                const research = activeResearch[0];
                // bearer:disable javascript_lang_dangerous_insert_html
                message.innerHTML = `
                    <strong>Research in progress:</strong> "${escapeHtml(research.query.substring(0, 50))}..."
                    <a href="/progress/${escapeHtml(research.id)}" class="ms-2 text-white">View Progress →</a>
                `;
            } else {
                statusDiv.style.display = 'none';
            }
        }
    } catch (error) {
        SafeLogger.error('Error checking priority status:', error);
    }

}

// Extract trending topics
function extractTrendingTopics() {
    // Collect all topics from news items
    const topicCounts = {};

    newsItems.forEach(item => {
        // Use the actual topics array if available
        if (item.topics && Array.isArray(item.topics)) {
            item.topics.forEach(topic => {
                if (topic && topic.trim()) {
                    topicCounts[topic] = (topicCounts[topic] || 0) + 1;
                }
            });
        }
    });

    // Sort topics by frequency and get top 15 with counts
    const topTopics = Object.entries(topicCounts)
        .sort((a, b) => b[1] - a[1])
        .slice(0, 15);  // Show more topics

    const container = document.getElementById('trending-topics');

    if (topTopics.length === 0) {
        container.innerHTML = '<div class="text-muted small">No trending topics available</div>';
    } else {
        // eslint-disable-next-line no-unsanitized/property -- audited 2026-03-28: all interpolations use escapeHtml/esc, numeric coercion, or hardcoded strings
        container.innerHTML = topTopics.map(([topic, count]) =>
            `<span class="ldr-topic-tag" data-topic="${escapeHtml(topic)}" title="Click to filter by ${escapeHtml(topic)} (${count} occurrences)">
                ${escapeHtml(topic)} <span class="ldr-topic-count">${count}</span>
            </span>`
        ).join('');
        // Event delegation is already handled in setupEventListeners
    }
}

// Create topic subscription
function createTopicSubscription(topic) {
    document.getElementById('news-query').value = topic;
    document.getElementById('news-search-form').dispatchEvent(new Event('submit'));
}

// Track active topic filter
let activeTopicFilter = null;

// Filter by topic
function filterByTopic(topic) {
    SafeLogger.log('filterByTopic called with:', topic);

    // Toggle filter if clicking the same topic
    if (activeTopicFilter === topic) {
        activeTopicFilter = null;
        showAlert('Showing all news', 'info');
    } else {
        activeTopicFilter = topic;
        showAlert(`Filtering by topic: ${topic}`, 'info');
    }

    // Update the UI to show active filter
    updateActiveTopicUI();
    updateFilterStatusBar();

    // Re-render the news items with filter applied
    renderNewsItems();
}

// Update UI to show active topic filter
function updateActiveTopicUI() {
    // Update all topic tags to show active state
    document.querySelectorAll('.ldr-topic-tag').forEach(tag => {
        const tagTopic = tag.getAttribute('data-topic');
        if (tagTopic === activeTopicFilter) {
            tag.classList.add('active');
        } else {
            tag.classList.remove('active');
        }
    });

    // Show/hide clear filter button
    const container = document.getElementById('news-feed-content');
    const existingFilter = container.querySelector('.ldr-active-filter-bar');

    if (activeTopicFilter) {
        if (!existingFilter) {
            const filterBar = document.createElement('div');
            filterBar.className = 'ldr-active-filter-bar';
            // bearer:disable javascript_lang_dangerous_insert_html
            filterBar.innerHTML = `
                <div class="ldr-filter-info">
                    <i class="bi bi-funnel-fill"></i>
                    Filtering by topic: <strong>${escapeHtml(activeTopicFilter)}</strong>
                    <button class="btn btn-sm btn-link" onclick="clearTopicFilter()">
                        <i class="bi bi-x-circle"></i> Clear filter
                    </button>
                </div>
            `;
            container.insertBefore(filterBar, container.firstChild);
        } else {
            existingFilter.querySelector('strong').textContent = activeTopicFilter;
        }
    } else if (existingFilter) {
        existingFilter.remove();
    }
}

// Clear topic filter
function clearTopicFilter() {
    activeTopicFilter = null;
    updateActiveTopicUI();
    renderNewsItems();
    showAlert('Filter cleared', 'info');
}

// Update bulk actions bar
function updateBulkActionsBar() {
    const feedContent = document.getElementById('news-feed-content');
    if (!feedContent) return;

    // Remove existing bulk actions bar
    const existingBar = feedContent.querySelector('.ldr-bulk-actions-bar');
    if (existingBar) existingBar.remove();

    // Only show if there are items
    if (newsItems.length === 0) return;

    const unreadCount = newsItems.filter(item => !readNewsIds.has(item.id)).length;
    const totalCount = newsItems.length;

    const bulkBar = document.createElement('div');
    bulkBar.className = 'ldr-bulk-actions-bar';
    // 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
    bulkBar.innerHTML = `
        <div class="ldr-bulk-actions-content">
            <div class="ldr-news-stats">
                <span class="ldr-stat-item">
                    <i class="bi bi-envelope"></i> ${unreadCount} unread
                </span>
                <span class="ldr-stat-item">
                    <i class="bi bi-newspaper"></i> ${totalCount} total
                </span>
            </div>
            <div class="ldr-bulk-actions">
                <button class="btn btn-sm btn-outline-secondary" onclick="markAllAsRead()">
                    <i class="bi bi-check-all"></i> Mark all as read
                </button>
                <button class="btn btn-sm btn-outline-secondary" onclick="expandAll()">
                    <i class="bi bi-arrows-expand"></i> Expand all
                </button>
                <button class="btn btn-sm btn-outline-secondary" onclick="collapseAll()">
                    <i class="bi bi-arrows-collapse"></i> Collapse all
                </button>
            </div>
        </div>
    `;

    // Insert at the beginning of feed content
    feedContent.insertBefore(bulkBar, feedContent.firstChild);
}

// Update filter status bar
function updateFilterStatusBar() {
    const feedHeader = document.querySelector('.ldr-feed-header');
    if (!feedHeader) return;

    // Remove existing filter status bar
    const existingBar = feedHeader.querySelector('.ldr-filter-status-bar');
    if (existingBar) existingBar.remove();

    // Check if any filters are active
    const hasFilters = activeTopicFilter || activeTimeFilter !== 'all' || activeImpactThreshold > 0;

    SafeLogger.log('updateFilterStatusBar called:', {
        activeTopicFilter,
        activeTimeFilter,
        activeImpactThreshold,
        hasFilters
    });

    if (hasFilters) {
        const filterBadges = [];

        if (activeTopicFilter) {
            filterBadges.push(`<span class="ldr-filter-badge">Topic: ${escapeHtml(activeTopicFilter)}</span>`);
        }
        if (activeTimeFilter !== 'all') {
            filterBadges.push(`<span class="ldr-filter-badge">Time: ${escapeHtml(activeTimeFilter)}</span>`);
        }
        if (activeImpactThreshold > 0) {
            filterBadges.push(`<span class="ldr-filter-badge">Impact ≥ ${activeImpactThreshold}</span>`);
        }

        // Only create and show the bar if we have actual filter badges
        if (filterBadges.length > 0) {
            const filterBar = document.createElement('div');
            filterBar.className = 'ldr-filter-status-bar';

            let filterHtml = '<div class="ldr-active-filters"><span>Active filters:</span>';
            filterHtml += filterBadges.join('');
            filterHtml += `<button class="btn btn-sm btn-link" onclick="clearAllFilters()">
                <i class="bi bi-x-circle"></i> Clear all
            </button></div>`;

            // 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
            filterBar.innerHTML = filterHtml;
            feedHeader.appendChild(filterBar);
        }
    }
}

// Update feed header based on active subscription
function updateFeedHeader() {
    const feedTitle = document.querySelector('.ldr-feed-header h2');
    if (!feedTitle) return;

    if (activeSubscription === 'all') {
        feedTitle.textContent = 'All News';
    } else if (activeSubscription === 'saved') {
        feedTitle.textContent = 'Saved Items';
    } else {
        // Find the subscription name
        const sub = subscriptions.find(s => s.id === activeSubscription);
        if (sub) {
            const query = sub.query || sub.topic || sub.query_or_topic || 'Unknown';
            // bearer:disable javascript_lang_dangerous_insert_html
            feedTitle.innerHTML = `News for: <span class="text-muted">${escapeHtml(query)}</span>`;
        }
    }
}

// Clear all filters
function clearAllFilters() {
    // Clear all filter states
    activeTopicFilter = null;
    activeTimeFilter = 'all';
    activeImpactThreshold = 0;

    // Clear semantic search state
    clearSemanticState();

    // Update UI
    updateActiveTopicUI();

    // Reset filter chips
    document.querySelectorAll('.ldr-time-filter-group .ldr-filter-btn').forEach(chip => {
        chip.classList.remove('active');
        if (chip.dataset.time === 'all') chip.classList.add('active');
    });

    // Reset impact slider
    const impactSlider = document.getElementById('impact-filter');
    if (impactSlider) {
        impactSlider.value = 0;
        document.querySelector('.ldr-slider-value').textContent = '0';
    }

    // Re-render
    renderNewsItems();
    showAlert('All filters cleared', 'info');
}

// Render source links
function renderSourceLinks(links) {
    if (!links || links.length === 0) {
        return '';
    }

    const linksHtml = links.map(link => {
        // Extract domain name from URL to save space
        let displayText = link.title;
        try {
            const url = new URL(link.url);
            const domain = url.hostname.replace('www.', '');
            // Use domain if title is too long
            if (link.title.length > 30) {
                displayText = domain;
            }
        } catch {
            // If URL parsing fails, truncate title
            displayText = link.title.length > 30 ? link.title.substring(0, 27) + '...' : link.title;
        }

        return `<a href="${safeHref(link.url)}" target="_blank" rel="noopener noreferrer" class="ldr-source-link" title="${escapeHtml(link.title)}">
            <i class="bi bi-link-45deg"></i>
            ${escapeHtml(displayText)}
        </a>`;
    }).join('');

    return `
        <div class="ldr-news-sources">
            <div class="ldr-sources-list">
                ${linksHtml}
            </div>
        </div>
    `;
}

// Show subscription history
async function showSubscriptionHistory(subscriptionId) {
    try {
        const response = await fetch(`/news/api/subscriptions/${subscriptionId}/history`);
        if (!response.ok) throw new Error('Failed to load history');

        const data = await response.json();

        // Create modal content
        // Security: escapeHtml applied to user-controlled fields from API response
        let historyHtml = '';
        if (data.history && data.history.length > 0) {
            historyHtml = data.history.map(item => `
                <div class="ldr-history-item">
                    <div class="ldr-history-header">
                        <span class="ldr-history-status ldr-status-${escapeHtml(item.status)}">${escapeHtml(item.status)}</span>
                        <span class="ldr-history-time">${escapeHtml(new Date(item.created_at).toLocaleString())}</span>
                    </div>
                    <div class="ldr-history-query">${escapeHtml(item.query)}</div>
                    <div class="ldr-history-actions">
                        <a href="${safeHref(item.url)}" class="btn btn-sm btn-primary">View Results</a>
                        ${item.duration_seconds ? `<span class="ldr-duration">${Number(item.duration_seconds) || 0}s</span>` : ''}
                    </div>
                </div>
            `).join('');
        } else {
            historyHtml = '<p class="text-muted">No history available yet.</p>';
        }

        // Create modal
        const modalHtml = `
            <div class="modal fade" id="subscriptionHistoryModal" tabindex="-1">
                <div class="modal-dialog modal-lg">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title">
                                Subscription History
                                <small class="text-muted">${escapeHtml(data.subscription.query)}</small>
                            </h5>
                            <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                        </div>
                        <div class="modal-body">
                            <div class="ldr-subscription-stats mb-3">
                                <span class="ldr-stat-item">
                                    <i class="bi bi-arrow-repeat"></i>
                                    ${Number(data.subscription.refresh_count) || 0} refreshes
                                </span>
                                <span class="ldr-stat-item">
                                    <i class="bi bi-clock"></i>
                                    Every ${Number(data.subscription.refresh_interval_minutes) || 0} min
                                </span>
                                <span class="ldr-stat-item">
                                    <i class="bi bi-calendar"></i>
                                    Since ${new Date(data.subscription.created_at).toLocaleDateString()}
                                </span>
                            </div>
                            <div class="ldr-history-list">
                                ${historyHtml}
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        `;

        // Remove existing modal if any
        const existingModal = document.getElementById('subscriptionHistoryModal');
        if (existingModal) existingModal.remove();

        // Add modal to page
        // bearer:disable javascript_lang_dangerous_insert_html
        // eslint-disable-next-line no-unsanitized/method -- audited 2026-03-28: HTML variable built with escaped values
        document.body.insertAdjacentHTML('beforeend', modalHtml);

        // Show modal
        const modal = new bootstrap.Modal(document.getElementById('subscriptionHistoryModal'));
        modal.show();

        // Clean up when modal is hidden
        document.getElementById('subscriptionHistoryModal').addEventListener('hidden.bs.modal', function() {
            this.remove();
        });

    } catch (error) {
        SafeLogger.error('Error loading subscription history:', error);
        showAlert('Failed to load subscription history', 'error');
    }
}

// Query template functions
function loadNewsTableQuery() {
    const template = document.getElementById('query-template');
    template.style.display = 'block';
    template.querySelector('.ldr-template-content').textContent = getNewsTableQuery();
}

function hideQueryTemplate() {
    document.getElementById('query-template').style.display = 'none';
}

function useQueryTemplate() {
    document.getElementById('news-query').value = getNewsTableQuery();
    hideQueryTemplate();
    document.getElementById('table-view-toggle').checked = true;
    showAlert('Query template loaded. Click Search to execute!', 'info');
}

async function copyQueryTemplate() {
    try {
        await navigator.clipboard.writeText(getNewsTableQuery());
        showAlert('Query copied to clipboard!', 'success');
    } catch (err) {
        SafeLogger.error('Failed to copy:', err);
        showAlert('Failed to copy query', 'error');
    }
}

// Subscription modal
function showCreateSubscriptionModal() {
    // Create modal HTML
    const modalHtml = `
        <div class="modal fade" id="createSubscriptionModal" tabindex="-1">
            <div class="modal-dialog">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">Create New Subscription</h5>
                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                    </div>
                    <div class="modal-body">
                        <div class="mb-3">
                            <label class="form-label">Subscription Type</label>
                            <div>
                                <div class="form-check form-check-inline">
                                    <input class="form-check-input" type="radio" name="sub-type" id="sub-type-search" value="search" checked>
                                    <label class="form-check-label" for="sub-type-search">Search Query</label>
                                </div>
                                <div class="form-check form-check-inline">
                                    <input class="form-check-input" type="radio" name="sub-type" id="sub-type-topic" value="topic">
                                    <label class="form-check-label" for="sub-type-topic">Topic</label>
                                </div>
                            </div>
                        </div>
                        <div class="mb-3">
                            <label for="sub-query" class="form-label">Query or Topic</label>
                            <input type="text" class="ldr-form-control" id="sub-query" placeholder="e.g., AI safety news, Ukraine conflict updates">
                        </div>
                        <div class="mb-3">
                            <label for="sub-refresh" class="form-label">Refresh Interval (hours)</label>
                            <select class="ldr-form-control" id="sub-refresh">
                                <option value="1">Every hour</option>
                                <option value="4" selected>Every 4 hours</option>
                                <option value="6">Every 6 hours</option>
                                <option value="12">Every 12 hours</option>
                                <option value="24">Daily</option>
                            </select>
                        </div>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
                        <button type="button" class="btn btn-primary" onclick="createSubscription()">Create Subscription</button>
                    </div>
                </div>
            </div>
        </div>
    `;

    // Remove existing modal if any
    const existingModal = document.getElementById('createSubscriptionModal');
    if (existingModal) existingModal.remove();

    // Add modal to page
    document.body.insertAdjacentHTML('beforeend', modalHtml);

    // Show modal
    const modal = new bootstrap.Modal(document.getElementById('createSubscriptionModal'));
    modal.show();

    // Clean up when modal is hidden
    document.getElementById('createSubscriptionModal').addEventListener('hidden.bs.modal', function() {
        this.remove();
    });
}

function hideSubscriptionModal() {
    const modal = bootstrap.Modal.getInstance(document.getElementById('createSubscriptionModal'));
    if (modal) modal.hide();
}

async function createSubscription() {
    const type = document.querySelector('input[name="sub-type"]:checked').value;
    const query = document.getElementById('sub-query').value;
    const refreshMinutes = document.getElementById('sub-refresh').value;

    if (!query) {
        showAlert('Please enter a query or topic', 'warning');
        return;
    }

    try {
        const response = await fetch('/news/api/subscribe', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCSRFToken()
            },
            body: JSON.stringify({
                query,
                subscription_type: type,
                refresh_minutes: parseInt(refreshMinutes, 10)
            })
        });

        if (response.ok) {
            hideSubscriptionModal();
            await loadSubscriptions();
            showAlert('Subscription created successfully!', 'success');
        } else {
            showAlert('Failed to create subscription', 'error');
        }
    } catch (error) {
        SafeLogger.error('Error creating subscription:', error);
        showAlert('Error creating subscription', 'error');
    }
}

// Utility functions
function showAlert(message, type = 'info') {
    const alertContainer = document.getElementById('news-alert');
    alertContainer.className = `ldr-settings-alert-container alert-${type}`;
    alertContainer.textContent = message;
    alertContainer.style.display = 'block';

    setTimeout(() => {
        alertContainer.style.display = 'none';
    }, 5000);
}

function getCSRFToken() {
    return (window.api && window.api.getCsrfToken)
        ? window.api.getCsrfToken()
        : (document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '');
}

// --- Semantic search ---

const NEWS_CARD_CONFIG = {
    getId: (r) => r.research_id || '',
    getTitle: (r) => r.research_title || r.title || 'Untitled',
    getUrl: (r) => ((typeof URLBuilder !== 'undefined' && r.research_id)
        ? URLBuilder.resultsPage(r.research_id) : '#'),
    getBadges: () => [{ icon: 'newspaper', label: 'News' }],
    getDate: (r) => r.research_created_at,
    getSubtitle: () => null,
};

async function initNewsSemanticSearch() {
    try {
        const resp = await fetch('/library/api/research-history/collection', {
            headers: { 'X-CSRFToken': getCSRFToken() }
        });
        if (!resp.ok) return;
        const data = await resp.json();
        if (data.success) newsCollectionId = data.collection_id;
    } catch (e) {
        SafeLogger.warn('Semantic search unavailable:', e);
    }
}

function setupSearchModeToggle() {
    const menu = document.getElementById('news-search-mode-menu');
    if (!menu) return;

    menu.addEventListener('click', function(e) {
        const item = e.target.closest('.dropdown-item');
        if (!item) return;
        e.preventDefault();

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

        newsSearchMode = mode;

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

        // Update button label
        const btn = document.getElementById('news-search-mode-btn');
        if (btn) {
            const labels = { hybrid: 'AI Hybrid', text: 'Text Only', semantic: 'AI Only' };
            const icons = { hybrid: 'fa-brain', text: 'fa-font', semantic: 'fa-brain' };
            if (labels[mode]) {
                window.safeUpdateButton(btn, icons[mode], ' ' + labels[mode]);
            }
        }

        // Update placeholder
        const input = document.getElementById('news-search');
        if (input) {
            const placeholders = {
                hybrid: 'AI Hybrid: searches headlines & content with AI...',
                text: 'Text: filters by headline, topic, or keyword...',
                semantic: 'AI: searches inside research content using semantic similarity...'
            };
            input.placeholder = placeholders[mode];
        }

        // Cancel any in-flight debounced search and clear semantic state
        clearTimeout(newsSemanticTimer);
        clearSemanticState();

        // Re-trigger search with current query
        const query = document.getElementById('news-search').value.trim();
        if (query) {
            handleSearchSubmit();
        }
    });
}

function clearSemanticState() {
    newsSemanticMatches = null;
    newsSearchId++;
    clearTimeout(newsSemanticTimer);
    const semanticContainer = document.getElementById('news-semantic-results');
    if (semanticContainer) {
        semanticContainer.style.display = 'none';
        semanticContainer.innerHTML = '';
    }
    const feedContent = document.getElementById('news-feed-content');
    if (feedContent) feedContent.style.display = '';
}

async function runNewsSemanticSearch(query) {
    const currentId = ++newsSearchId;

    if (!newsCollectionId) {
        showAlert('Semantic search not available — research history may not be indexed yet.', 'warning');
        return;
    }

    const feedContent = document.getElementById('news-feed-content');
    const semanticContainer = document.getElementById('news-semantic-results');
    if (!semanticContainer) return;

    // Show loading
    if (feedContent) feedContent.style.display = 'none';
    semanticContainer.style.display = '';
    semanticContainer.innerHTML = '<div class="ldr-loading-placeholder"><div class="ldr-loading-spinner"></div><p>Searching content with AI...</p></div>';

    try {
        const searchUrl = typeof URLBuilder !== 'undefined'
            ? URLBuilder.build(URLS.LIBRARY_API.COLLECTION_SEARCH, newsCollectionId)
            : `/library/api/collections/${encodeURIComponent(newsCollectionId)}/search`;

        const resp = await fetch(searchUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCSRFToken()
            },
            body: JSON.stringify({ query, limit: 50 })
        });

        // Stale guard
        if (currentId !== newsSearchId || newsSearchMode !== NEWS_SM.SEMANTIC) return;

        if (!resp.ok) {
            semanticContainer.innerHTML = '<div class="ldr-empty-state"><p>Semantic search failed. Try again later.</p></div>';
            return;
        }

        const data = await resp.json();
        if (currentId !== newsSearchId || newsSearchMode !== NEWS_SM.SEMANTIC) return;

        let results = data.results || [];

        // Filter to only news items
        const newsIds = new Set(newsItems.map(i => String(i.research_id)));
        results = results.filter(r => r.research_id && newsIds.has(String(r.research_id)));

        if (results.length === 0) {
            semanticContainer.innerHTML = '<div class="ldr-empty-state"><i class="fas fa-brain fa-2x" style="opacity:0.5"></i><p>No semantic matches found in news items.</p></div>';
            return;
        }

        // Render semantic result cards
        const createCard = window.SemanticSearch && window.SemanticSearch.createSemanticResultCard;
        if (!createCard) {
            semanticContainer.innerHTML = '<div class="ldr-empty-state"><i class="fas fa-exclamation-triangle fa-2x"></i><p>Search module not loaded. Please refresh the page.</p></div>';
            return;
        }

        semanticContainer.innerHTML = '';
        for (const result of results) {
            semanticContainer.appendChild(createCard(result, NEWS_CARD_CONFIG, query));
        }
    } catch (error) {
        SafeLogger.error('Semantic search error:', error);
        semanticContainer.innerHTML = '<div class="ldr-empty-state"><p>Error performing semantic search.</p></div>';
    }
}

async function runNewsHybridSearch(query) {
    const currentId = ++newsSearchId;

    // Show loading indicator
    const feedContent = document.getElementById('news-feed-content');
    const existingIndicator = document.getElementById('news-hybrid-loading');
    if (existingIndicator) existingIndicator.remove();

    if (feedContent) {
        const loadingDiv = document.createElement('div');
        loadingDiv.className = 'ldr-hybrid-loading';
        loadingDiv.id = 'news-hybrid-loading';
        loadingDiv.innerHTML = '<div class="ldr-spinner" style="width: 16px; height: 16px; border-width: 2px;"></div> Searching content...';
        feedContent.appendChild(loadingDiv);
    }

    try {
        if (!newsCollectionId) {
            const indicator = document.getElementById('news-hybrid-loading');
            if (indicator) indicator.remove();
            return;
        }

        const searchUrl = typeof URLBuilder !== 'undefined'
            ? URLBuilder.build(URLS.LIBRARY_API.COLLECTION_SEARCH, newsCollectionId)
            : `/library/api/collections/${encodeURIComponent(newsCollectionId)}/search`;

        const resp = await fetch(searchUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCSRFToken()
            },
            body: JSON.stringify({ query, limit: 50 })
        });

        // Stale guard
        if (currentId !== newsSearchId || newsSearchMode !== NEWS_SM.HYBRID) {
            const indicator = document.getElementById('news-hybrid-loading');
            if (indicator) indicator.remove();
            return;
        }

        if (!resp.ok) {
            const indicator = document.getElementById('news-hybrid-loading');
            if (indicator) indicator.remove();
            return;
        }

        const data = await resp.json();
        if (currentId !== newsSearchId || newsSearchMode !== NEWS_SM.HYBRID) {
            const indicator = document.getElementById('news-hybrid-loading');
            if (indicator) indicator.remove();
            return;
        }

        // Filter to news items
        const newsIds = new Set(newsItems.map(i => String(i.research_id)));
        const semanticResults = (data.results || []).filter(r => r.research_id && newsIds.has(String(r.research_id)));

        // Build tiered results
        const buildTiered = window.SemanticSearch && window.SemanticSearch.buildTieredResults;
        if (!buildTiered) {
            const indicator = document.getElementById('news-hybrid-loading');
            if (indicator) indicator.remove();
            return;
        }

        const textItems = newsItems.map(item => ({ id: String(item.research_id), item }));
        const tiered = buildTiered(textItems, semanticResults, { textIdKey: 'id', semanticIdKey: 'research_id' });

        // Reorder newsItems array
        const reordered = [];
        const similarityMap = new Map();
        for (const entry of tiered.tier1) {
            reordered.push(entry.historyItem.item);
            similarityMap.set(String(entry.historyItem.item.research_id), entry.semanticMatch);
        }
        for (const entry of tiered.tier2) {
            reordered.push(entry.historyItem.item);
        }

        // Store similarity data for badge rendering
        newsSemanticMatches = similarityMap;

        // Replace newsItems and re-render
        newsItems = reordered;
        renderNewsItems(query);

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

        // Tier 3: semantic-only results not in current feed
        if (tiered.tier3.length > 0 && feedContent) {
            const createCard = window.SemanticSearch && window.SemanticSearch.createSemanticResultCard;
            if (createCard) {
                const divider = document.createElement('div');
                divider.className = 'ldr-hybrid-divider';
                divider.textContent = 'Also found in research content';
                feedContent.appendChild(divider);

                for (const entry of tiered.tier3) {
                    feedContent.appendChild(createCard(entry.semanticResult, NEWS_CARD_CONFIG, query));
                }
            }
        }
    } catch (error) {
        SafeLogger.error('Hybrid search error:', error);
        const indicator = document.getElementById('news-hybrid-loading');
        if (indicator) indicator.remove();
    }
}

// Refresh feed
function refreshFeed() {
    lastRefreshTime = new Date();
    updateRefreshIndicator();
    const query = document.getElementById('news-search').value.trim();
    clearSemanticState();
    if (query) {
        handleSearchSubmit();
    } else {
        loadNewsFeed();
    }
    showAlert('Feed refreshed', 'info');
}

// Auto-refresh functions
function startAutoRefresh() {
    // Clear any existing interval
    stopAutoRefresh();

    // Set refresh interval to 5 minutes
    const refreshInterval = 5 * 60 * 1000; // 5 minutes

    autoRefreshInterval = setInterval(() => {
        // Skip auto-refresh while any search query is active
        if (document.getElementById('news-search').value.trim()) return;
        refreshFeed();
    }, refreshInterval);

    // Update indicator every second (clear any existing first)
    if (refreshIndicatorInterval) {
        clearInterval(refreshIndicatorInterval);
    }
    refreshIndicatorInterval = setInterval(updateRefreshIndicator, 1000);

    showAlert('Auto-refresh enabled (every 5 minutes)', 'success');
}

function stopAutoRefresh() {
    if (autoRefreshInterval) {
        clearInterval(autoRefreshInterval);
        autoRefreshInterval = null;
    }
    if (refreshIndicatorInterval) {
        clearInterval(refreshIndicatorInterval);
        refreshIndicatorInterval = null;
    }
    showAlert('Auto-refresh disabled', 'info');
}

function updateRefreshIndicator() {
    const timeSinceRefresh = Math.floor((new Date() - lastRefreshTime) / 1000);
    const minutes = Math.floor(timeSinceRefresh / 60);
    const seconds = timeSinceRefresh % 60;

    // Update refresh button text if it exists
    const refreshBtn = document.querySelector('button[onclick="refreshFeed()"] i');
    if (refreshBtn) {
        const timeText = minutes > 0 ? `${minutes}m ago` : `${seconds}s ago`;
        refreshBtn.setAttribute('title', `Last refresh: ${timeText}`);

        // Add visual indicator when it's been more than 5 minutes
        if (minutes >= 5) {
            refreshBtn.classList.add('text-warning');
        } else {
            refreshBtn.classList.remove('text-warning');
        }
    }

    // Add countdown to next refresh if auto-refresh is active
    if (autoRefreshInterval) {
        const nextRefreshIn = 300 - timeSinceRefresh; // 5 minutes = 300 seconds
        if (nextRefreshIn > 0) {
            const nextMinutes = Math.floor(nextRefreshIn / 60);
            const nextSeconds = nextRefreshIn % 60;
            const countdownText = `Next refresh in ${nextMinutes}:${nextSeconds.toString().padStart(2, '0')}`;

            // Update auto-refresh label
            const autoRefreshLabel = document.querySelector('label[for="auto-refresh"]');
            if (autoRefreshLabel) {
                // 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
                autoRefreshLabel.innerHTML = `<i class="bi bi-arrow-clockwise"></i> Auto-refresh (${countdownText})`;
            }
        }
    }
}

// Monitor a specific research by ID
async function monitorResearch(researchId, query = null) {
    SafeLogger.log('Monitoring research:', researchId);

    // Store in localStorage so it persists across page loads
    localStorage.setItem('active_news_research', JSON.stringify({
        researchId,
        query,
        startTime: new Date().toISOString()
    }));

    // First, get the initial status and query
    try {
        const statusResponse = await fetch(URLBuilder.researchStatus(researchId));
        if (statusResponse.ok) {
            const statusData = await statusResponse.json();
            query ||= statusData.query || 'News Analysis';

            // Show the progress card immediately
            const container = document.getElementById('news-feed-content');
            const progressCard = document.querySelector(`[data-research-id="${researchId}"]`);

            if (!progressCard) {
                // Create new progress card at the top
                const newCard = document.createElement('div');
                newCard.className = 'ldr-news-card ldr-priority-high ldr-active-research-card';
                newCard.setAttribute('data-research-id', researchId);
                // 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
                newCard.innerHTML = `
                    <div class="ldr-news-header">
                        <h2 class="ldr-news-title">
                            <i class="bi bi-hourglass-split ldr-spinning"></i>
                            Analyzing: "${escapeHtml(query.substring(0, 60))}..."
                        </h2>
                    </div>
                    <div class="ldr-news-meta">
                        <span><i class="bi bi-info-circle"></i> Research ID: ${escapeHtml(researchId)}</span>
                        <span><i class="bi bi-clock"></i> ${ResearchStates.isInProgress(statusData.status) ? 'In progress' : 'Started just now'}</span>
                    </div>
                    <div class="ldr-news-summary">
                        <div class="progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${Number(statusData.progress) || 10}" aria-label="Research progress">
                            <div class="progress-bar progress-bar-striped progress-bar-animated" style="width: ${Number(statusData.progress) || 10}%"></div>
                        </div>
                        <p class="mt-2">${escapeHtml(statusData.message || 'Searching for breaking news and analyzing importance...')}</p>
                    </div>
                `;

                // Insert at the beginning
                if (container.firstChild) {
                    container.insertBefore(newCard, container.firstChild);
                } else {
                    container.appendChild(newCard);
                }
            }
        }
    } catch (error) {
        SafeLogger.error('Error getting initial status:', error);
    }

    // Now start polling for updates
    const checkInterval = setInterval(async () => {
        try {
            const response = await fetch(URLBuilder.researchStatus(researchId));
            if (response.ok) {
                const data = await response.json();
                SafeLogger.log('Research status:', data.status, 'Progress:', data.progress);

                // Update progress card
                const progressCard = document.querySelector(`[data-research-id="${researchId}"]`);
                if (progressCard && ResearchStates.isInProgress(data.status)) {
                    const progressValue = data.progress || 10;
                    // Update ARIA on container and width on fill
                    const progressContainer = progressCard.querySelector('[role="progressbar"]');
                    if (progressContainer) {
                        progressContainer.setAttribute('aria-valuenow', progressValue);
                        const progressFill = progressContainer.firstElementChild;
                        if (progressFill) {
                            progressFill.style.width = `${progressValue}%`;
                        }
                    }
                    const progressText = progressCard.querySelector('.ldr-news-summary p');
                    if (progressText && data.message) {
                        progressText.textContent = data.message;
                    }
                }

                if (ResearchStates.isCompleted(data.status)) {
                    clearInterval(checkInterval);
                    localStorage.removeItem('active_news_research'); // Clear from localStorage
                    SafeLogger.log('Research completed, reloading news feed');

                    // Remove the progress card
                    const card = document.querySelector(`[data-research-id="${researchId}"]`);
                    if (card) {
                        card.remove();
                    }

                    // Show success message
                    showAlert('Test run completed! Loading results...', 'success');

                    // Reload the news feed to show the completed research
                    // The backend should now include it because it has is_news_search metadata
                    setTimeout(() => {
                        loadNewsFeed();
                    }, 1000);
                } else if (ResearchStates.isFailed(data.status)) {
                    clearInterval(checkInterval);
                    localStorage.removeItem('active_news_research'); // Clear from localStorage
                    // Remove progress card
                    const card = document.querySelector(`[data-research-id="${researchId}"]`);
                    if (card) {
                        card.remove();
                    }
                    showAlert('Test run failed. Please check your configuration and try again.', 'error');
                }
            }
        } catch (error) {
            SafeLogger.error('Error checking research status:', error);
        }
    }, 3000); // Check every 3 seconds

    // Stop checking after 5 minutes
    setTimeout(() => {
        clearInterval(checkInterval);
    }, 5 * 60 * 1000);
}

// Check for active news research on page load
async function checkActiveNewsResearch() {
    try {
        // Check localStorage for active news research
        const activeResearch = localStorage.getItem('active_news_research');
        if (!activeResearch) return;

        const { researchId, query, startTime } = JSON.parse(activeResearch);

        // Check if research is still recent (within 10 minutes)
        const elapsed = Date.now() - new Date(startTime).getTime();
        if (elapsed > 10 * 60 * 1000) {
            localStorage.removeItem('active_news_research');
            return;
        }

        // Check research status
        const statusResponse = await fetch(URLBuilder.researchStatus(researchId));
        if (!statusResponse.ok) {
            localStorage.removeItem('active_news_research');
            return;
        }

        const status = await statusResponse.json();

        if (ResearchStates.isInProgress(status.status)) {
            // Use monitorResearch to show the progress card and handle polling
            monitorResearch(researchId, query);
        } else if (ResearchStates.isCompleted(status.status)) {
            // Research completed while user was away
            localStorage.removeItem('active_news_research');
            showAlert('Your news analysis has completed! Loading results...', 'success');

            // Reload the feed to show the results
            await loadNewsFeed();
        } else {
            // Research failed or was cancelled
            localStorage.removeItem('active_news_research');
        }
    } catch (error) {
        SafeLogger.error('Error checking active research:', error);
        localStorage.removeItem('active_news_research');
    }
}

// Poll for news research results
async function pollForNewsResearchResults(researchId, originalQuery, isResume = false) {
    let pollCount = 0;
    const maxPolls = 60; // 5 minutes max

    // Store active research in localStorage
    if (!isResume) {
        localStorage.setItem('active_news_research', JSON.stringify({
            researchId,
            query: originalQuery,
            startTime: new Date().toISOString()
        }));
    }

    const pollInterval = setInterval(async () => {
        try {
            // Check research status
            const statusResponse = await fetch(URLBuilder.researchStatus(researchId));
            if (!statusResponse.ok) {
                clearInterval(pollInterval);
                showAlert('Failed to check research status', 'error');
                return;
            }

            const status = await statusResponse.json();

            // Update progress bar (scoped to the active research card)
            const activeCard = document.querySelector('.ldr-active-research-card');
            if (activeCard) {
                const progressValue = status.progress || 10;
                // Update ARIA on container and width on fill
                const progressContainer = activeCard.querySelector('[role="progressbar"]');
                if (progressContainer) {
                    progressContainer.setAttribute('aria-valuenow', progressValue);
                    const progressFill = progressContainer.firstElementChild;
                    if (progressFill) {
                        progressFill.style.width = `${progressValue}%`;
                    }
                }
            }

            if (ResearchStates.isCompleted(status.status)) {
                clearInterval(pollInterval);
                localStorage.removeItem('active_news_research');

                // Remove any progress card
                const progressCards = document.querySelectorAll('.ldr-active-research-card');
                progressCards.forEach(card => card.remove());

                // Show success and reload feed
                showAlert('News analysis completed! Loading results...', 'success');

                // Reload the news feed after a short delay
                // The backend should now include the completed research
                setTimeout(() => {
                    loadNewsFeed();
                }, 1000);
            } else if (ResearchStates.isTerminal(status.status) && !ResearchStates.isCompleted(status.status)) {
                clearInterval(pollInterval);
                localStorage.removeItem('active_news_research');
                showAlert(`Research ${status.status}: ${status.metadata?.error || 'Unknown error'}`, 'error');
                loadNewsFeed(); // Reload normal feed
            }

            pollCount++;
            if (pollCount >= maxPolls) {
                clearInterval(pollInterval);
                localStorage.removeItem('active_news_research');
                showAlert('Research taking too long. Check the progress page.', 'warning');
            }
        } catch (error) {
            SafeLogger.error('Error polling for results:', error);
            clearInterval(pollInterval);
            localStorage.removeItem('active_news_research');
            showAlert('Error checking research status', 'error');
        }
    }, 5000); // Poll every 5 seconds
}



// Search history functions
async function loadSearchHistory() {
    try {
        SafeLogger.log('Loading search history from /news/api/search-history');
        const response = await fetch('/news/api/search-history', {
            method: 'GET',
            credentials: 'same-origin',
            headers: {
                'Accept': 'application/json',
                'X-CSRFToken': getCSRFToken()
            }
        });
        SafeLogger.log('Search history response status:', response.status);
        SafeLogger.log('Search history response headers:', response.headers);

        if (response.ok) {
            const data = await response.json();
            SafeLogger.log('Search history data:', data);
            searchHistory = data.search_history || [];
            displayRecentSearches();
        } else if (response.status === 401 || response.status === 302) {
            // User not authenticated or redirected to login
            SafeLogger.log('User not authenticated for search history');
            searchHistory = [];
            displayRecentSearches();
        } else {
            SafeLogger.error('Unexpected response status:', response.status);
            const text = await response.text();
            SafeLogger.error('Response text:', text);
            searchHistory = [];
            displayRecentSearches();
        }
    } catch (e) {
        SafeLogger.error('Failed to load search history:', e);
        searchHistory = [];
        displayRecentSearches();
    }
}

async function saveSearchHistory(query, type, resultCount) {
    try {
        SafeLogger.log('Saving search history:', { query, type, resultCount });
        const response = await fetch('/news/api/search-history', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCSRFToken()
            },
            credentials: 'same-origin',
            body: JSON.stringify({
                query,
                type,
                resultCount
            })
        });
        SafeLogger.log('Save search history response:', response.status);
        const data = await response.json();
        SafeLogger.log('Save search history data:', data);
    } catch (e) {
        SafeLogger.error('Failed to save search history:', e);
    }
}

async function addToSearchHistory(query, type = 'quick') {
    // Save to database
    await saveSearchHistory(query, type, newsItems.length);

    // Reload history from server to get the updated list
    await loadSearchHistory();
}

function displayRecentSearches() {
    const container = document.getElementById('recent-searches');
    if (!container) return;

    if (searchHistory.length === 0) {
        container.innerHTML = '<div class="text-muted small">Your recent news searches will appear here</div>';
        return;
    }

    const html = searchHistory.slice(0, 10).map(item => {
        const date = new Date(item.timestamp);
        const timeAgo = getTimeAgo(date);
        const typeIcon = item.type === 'deep' ? 'bi-search-heart' :
                        item.type === 'table' ? 'bi-table' : 'bi-search';

        return `
            <div class="ldr-recent-search-item" onclick="rerunSearch('${escapeAttr(item.query)}', '${escapeAttr(item.type)}')">
                <i class="bi ${typeIcon}"></i>
                <div class="flex-grow-1">
                    <div class="ldr-search-query">${escapeHtml(item.query)}</div>
                    <div class="ldr-search-meta">${timeAgo} • ${Number(item.resultCount) || 0} results</div>
                </div>
                <i class="bi bi-arrow-repeat"></i>
            </div>
        `;
    }).join('');

    safeRenderHTML(container, html);
}

function rerunSearch(query, _type = 'quick') {
    // Fill the search input
    document.getElementById('news-search').value = query;

    // Trigger search
    handleSearchSubmit();
}
async function clearSearchHistory() {
    if (!confirm('Clear all search history?')) return;

    try {
        const response = await fetch('/news/api/search-history', {
            method: 'DELETE',
            credentials: 'same-origin',
            headers: {
                'X-CSRFToken': getCSRFToken()
            }
        });

        if (response.ok) {
            searchHistory = [];
            displayRecentSearches();
            showAlert('Search history cleared', 'success');
        } else {
            showAlert('Failed to clear search history', 'danger');
        }
    } catch (e) {
        SafeLogger.error('Failed to clear search history:', e);
        showAlert('Failed to clear search history', 'danger');
    }
}

function getTimeAgo(date) {
    const seconds = Math.floor((new Date() - date) / 1000);

    if (seconds < 60) return 'just now';
    if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
    if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
    if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`;
    return date.toLocaleDateString();
}

// Check if item is within time filter
function isWithinTimeFilter(item, timeFilter) {
    // Try multiple date fields
    const itemDate = item.created_at || item.date || item.timestamp || item.published_at;
    if (!itemDate) return true; // If no date, include it

    const date = new Date(itemDate);
    const now = new Date();
    const hoursDiff = (now - date) / (1000 * 60 * 60);
    const daysDiff = hoursDiff / 24;

    switch (timeFilter) {
        case 'hour':
            return hoursDiff <= 1;
        case 'today':
            return daysDiff < 1;
        case 'week':
            return daysDiff <= 7;
        case 'month':
            return daysDiff <= 30;
        default:
            return true;
    }
}

// Read status functions
function loadReadStatus() {
    const stored = localStorage.getItem('news_read_ids');
    if (stored) {
        try {
            const ids = JSON.parse(stored);
            readNewsIds = new Set(ids);
        } catch {
            readNewsIds = new Set();
        }
    }
}

function saveReadStatus() {
    localStorage.setItem('news_read_ids', JSON.stringify(Array.from(readNewsIds)));
}

function markAsRead(newsId) {
    readNewsIds.add(newsId);
    // Update UI immediately
    const element = document.querySelector(`[data-news-id="${newsId}"]`);
    if (element) {
        element.classList.add('ldr-is-read');
        element.classList.remove('ldr-is-unread');
    }
}

function markAsReadOnClick(newsId) {
    // Mark as read when clicking on the report link
    if (!readNewsIds.has(newsId)) {
        markAsRead(newsId);
        saveReadStatus();
    }
    // Don't prevent default - let the link navigate
    return true;
}

function markAsUnread(newsId) {
    readNewsIds.delete(newsId);
    // Update UI immediately
    const element = document.querySelector(`[data-news-id="${newsId}"]`);
    if (element) {
        element.classList.remove('ldr-is-read');
        element.classList.add('ldr-is-unread');
    }
}

function toggleReadStatus(newsId) {
    if (readNewsIds.has(newsId)) {
        markAsUnread(newsId);
    } else {
        markAsRead(newsId);
    }
    saveReadStatus();
}

function markAllAsRead() {
    newsItems.forEach(item => {
        readNewsIds.add(item.id);
    });
    saveReadStatus();
    renderNewsItems();
    showAlert('All items marked as read', 'success');
}

// Expand all items
function expandAll() {
    document.querySelectorAll('.ldr-news-item').forEach(item => {
        const newsId = item.dataset.newsId;
        if (!expandedNewsIds.has(newsId)) {
            expandedNewsIds.add(newsId);
            item.classList.add('ldr-is-expanded');
            const icon = item.querySelector('.ldr-expand-icon');
            if (icon) icon.style.transform = 'rotate(180deg)';
        }
    });
    showAlert('All items expanded', 'info');
}

// Collapse all items
function collapseAll() {
    expandedNewsIds.clear();
    document.querySelectorAll('.ldr-news-item').forEach(item => {
        item.classList.remove('ldr-is-expanded');
        const icon = item.querySelector('.ldr-expand-icon');
        if (icon) icon.style.transform = 'rotate(0)';
    });
    showAlert('All items collapsed', 'info');
}

// Share and export functions
function shareNews(newsId) {
    const item = newsItems.find(n => n.id === newsId);
    if (!item) return;

    const shareUrl = window.location.origin + (item.source_url || `/results/${item.research_id}`);
    const shareText = `${item.headline}\n\n${shareUrl}`;

    if (navigator.share) {
        // Use Web Share API if available
        navigator.share({
            title: item.headline,
            text: item.summary || item.findings?.substring(0, 200) + '...',
            url: shareUrl
        }).then(() => {
            showAlert('Shared successfully', 'success');
        }).catch(err => {
            if (err.name !== 'AbortError') {
                copyToClipboard(shareText);
            }
        });
    } else {
        // Fallback to copy
        copyToClipboard(shareText);
    }
}

function copyNewsLink(newsId) {
    const item = newsItems.find(n => n.id === newsId);
    if (!item) return;

    const url = window.location.origin + (item.source_url || `/results/${item.research_id}`);
    copyToClipboard(url);
}

function exportToMarkdown(newsId) {
    const item = newsItems.find(n => n.id === newsId);
    if (!item) return;

    let markdown = `# ${item.headline}\n\n`;
    markdown += `**Category:** ${item.category || 'General'}\n`;
    markdown += `**Impact Score:** ${item.impact_score}/10\n`;
    markdown += `**Date:** ${new Date(item.created_at || Date.now()).toLocaleDateString()}\n\n`;

    if (item.findings) {
        markdown += `## Key Findings\n\n${item.findings}\n\n`;
    } else if (item.summary) {
        markdown += `## Summary\n\n${item.summary}\n\n`;
    }

    if (item.topics && item.topics.length > 0) {
        markdown += `## Topics\n\n${item.topics.map(t => `- ${t}`).join('\n')}\n\n`;
    }

    if (item.links && item.links.length > 0) {
        markdown += `## Sources\n\n${item.links.map(l => `- [${l.title}](${l.url})`).join('\n')}\n\n`;
    }

    const url = window.location.origin + (item.source_url || `/results/${item.research_id}`);
    markdown += `\n[View Full Report](${url})`;

    // Create and download file
    const blob = new Blob([markdown], { type: 'text/markdown' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `${item.headline.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.md`;
    a.click();

    showAlert('Exported to Markdown', 'success');
}

function hideNewsItem(newsId) {
    const element = document.querySelector(`[data-news-id="${newsId}"]`);
    if (element) {
        element.style.transition = 'opacity 0.3s, transform 0.3s';
        element.style.opacity = '0';
        element.style.transform = 'translateX(-20px)';
        setTimeout(() => {
            element.remove();
            // Remove from newsItems array
            newsItems = newsItems.filter(item => item.id !== newsId);
            updateBulkActionsBar();
        }, 300);
    }
    showAlert('Item hidden', 'info');
}


function copyToClipboard(text) {
    if (navigator.clipboard) {
        navigator.clipboard.writeText(text).then(() => {
            showAlert('Copied to clipboard', 'success');
        }).catch(_err => {
            // Fallback
            const textarea = document.createElement('textarea');
            textarea.value = text;
            textarea.style.position = 'fixed';
            textarea.style.opacity = '0';
            document.body.appendChild(textarea);
            textarea.select();
            document.execCommand('copy');
            document.body.removeChild(textarea);
            showAlert('Copied to clipboard', 'success');
        });
    }
}

// Visit tracking functions
function loadVisitTracking() {
    // Load last visit time
    const storedLastVisit = localStorage.getItem('news_last_visit');
    if (storedLastVisit) {
        lastVisitTime = new Date(storedLastVisit);
    } else {
        lastVisitTime = new Date();
    }

    // Load seen news IDs
    const storedSeenIds = localStorage.getItem('news_seen_ids');
    if (storedSeenIds) {
        try {
            const ids = JSON.parse(storedSeenIds);
            seenNewsIds = new Set(ids);
        } catch {
            seenNewsIds = new Set();
        }
    }

    // Clean up old seen IDs (keep only last 100)
    if (seenNewsIds.size > 100) {
        const idsArray = Array.from(seenNewsIds);
        seenNewsIds = new Set(idsArray.slice(-100));
    }
}

function saveVisitTracking() {
    // Save current time as last visit
    localStorage.setItem('news_last_visit', new Date().toISOString());

    // Save seen news IDs
    localStorage.setItem('news_seen_ids', JSON.stringify(Array.from(seenNewsIds)));
}

function markNewsAsSeen(newsId) {
    seenNewsIds.add(newsId);
    // Remove the new indicator after a delay
    setTimeout(() => {
        const element = document.querySelector(`[data-news-id="${newsId}"]`);
        if (element) {
            element.classList.remove('ldr-is-new', 'ldr-fade-in-new');
        }
    }, 5000); // Remove after 5 seconds
}

function isNewsNew(item) {
    // Check if we've seen this item before
    if (seenNewsIds.has(item.id)) {
        return false;
    }

    // Check if the item was created after last visit
    if (item.created_at && lastVisitTime) {
        const itemDate = new Date(item.created_at);
        return itemDate > lastVisitTime;
    }

    // If no timestamp, consider it new if we haven't seen it
    return true;
}

// News Templates
const newsTemplates = {
    'breaking-news': {
        name: 'Breaking News Table',
        query: `Find UP TO 10 IMPORTANT breaking news stories from TODAY YYYY-MM-DD ONLY.

START YOUR RESPONSE DIRECTLY WITH THE TABLE. NO INTRODUCTION OR PREAMBLE.

CRITICAL: All information MUST be from real, verifiable sources. DO NOT invent or fabricate any news, events, or details. Only report what you find from actual news sources.

OUTPUT FORMAT: Begin immediately with a markdown table using this exact structure:

| SOURCES | DATE | HEADLINE | CATEGORY | WHAT HAPPENED | ANALYSIS | IMPACT |
|---------|------|----------|----------|---------------|----------|--------|
| [Citation numbers] | [YYYY-MM-DD] | [Descriptive headline] | [War/Security/Economy/Disaster/Politics/Tech] | [3 sentences max from actual sources] | [Why it matters + What happens next + Historical context] | [1-10 score] | [Status] |

IMPORTANT: In the SOURCES column, list the citation numbers (e.g., [1, 3, 5]) that support this news item. These should match the numbered references at the end of your report.

Example row:
| [2, 4, 7] | 2025-01-12 | Major Earthquake Strikes Southern California | Disaster | A 7.1 magnitude earthquake hit near Los Angeles causing structural damage. Emergency services report multiple injuries. Aftershocks continue to affect the region. | Major infrastructure test for earthquake preparedness systems. FEMA mobilizing resources. Similar to 1994 Northridge event but with modern building codes. | 9 | Ongoing |

SEARCH STRATEGY:
1. breaking news today major incident developing impact
2. crisis death disaster announcement today hours ago
3. site:reuters.com OR site:bbc.com OR site:cnn.com today
4. geopolitical economic security emergency today

PRIORITIZE BY REAL-WORLD IMPACT:
- Active conflicts with casualties (Impact 8-10)
- Natural disasters affecting thousands (Impact 7-9)
- Economic shocks (>3% market moves) (Impact 6-8)
- Major political shifts (Impact 5-7)
- Critical infrastructure failures (Impact 6-9)

DIVERSITY IS MANDATORY: You MUST find 10 COMPLETELY DIFFERENT events. Examples of what NOT to do:
- ✗ BAD: Multiple stories about the same earthquake (initial report, death toll update, rescue efforts)
- ✗ BAD: Multiple stories about the same political event (announcement, reactions, analysis)
- ✓ GOOD: One earthquake in Japan, one political crisis in Europe, one economic news from US, etc.

If you can only find 7 truly distinct events, show 7. Do NOT pad with duplicate coverage.

REQUIREMENTS:
- Only include stories found from real sources
- Sort by IMPACT score (highest first)
- Each row must be complete with all columns filled
- Keep analysis concise but informative
- Verify sources are from today
- If insufficient real news is found, include fewer rows rather than inventing content

After the table, add:
- **THE BIG PICTURE**: One paragraph connecting today's major events (based on actual findings)
- **WATCH FOR**: 3 bullet points on what to monitor in next 24 hours`
    },
    'market-analysis': {
        name: 'Market Analysis',
        query: `Analyze today's financial markets and economic news from YYYY-MM-DD.

Find UP TO 10 SIGNIFICANT market movements, economic events, or financial news items from today.

CRITICAL: All information MUST be from real, verifiable sources. DO NOT invent or fabricate any market data or movements. Only report actual market information from reliable financial sources.

Create a comprehensive table with these columns:
| SOURCES | DATE | Market/Asset | Movement | Key Drivers | Analysis | Outlook |
|---------|------|--------------|----------|-------------|----------|----------|
| [Citation numbers] | [YYYY-MM-DD] | [Asset name] | [% change] | [Main reason] | [Impact analysis] | [Next 24h outlook] |

IMPORTANT: In the SOURCES column, list the citation numbers (e.g., [1, 3, 5]) that support this data. These should match the numbered references at the end of your report.

Focus on finding 10 DISTINCT market events:
- Major stock indices (S&P 500, NASDAQ, DOW)
- Cryptocurrency movements
- Commodity prices (Oil, Gold, etc.)
- Currency pairs
- Notable company earnings or news
- IPOs, mergers, or acquisitions
- Central bank decisions
- Economic indicators (GDP, inflation, employment)

Each row should be a SEPARATE market event or movement, not multiple updates about the same thing.

Include market sentiment analysis and tomorrow's key events to watch.

REQUIREMENTS:
- Only use real market data from verified sources
- Do not invent prices, movements, or market events
- If data is unavailable, state so rather than fabricating`
    },
    'tech-updates': {
        name: 'Technology Updates',
        query: `Find UP TO 10 IMPORTANT technology news stories and updates from today YYYY-MM-DD.

CRITICAL: All information MUST be from real, verifiable sources. DO NOT invent or fabricate any tech news, announcements, or details. Only report what you find from actual technology news sources.

Format as a table:
| SOURCES | DATE | Company/Topic | Announcement | Impact | Technical Details | Market Reaction |
|---------|------|---------------|--------------|--------|-------------------|------------------|
| [Citation numbers] | [YYYY-MM-DD] | [Company/Tech] | [What was announced] | [Industry impact] | [Technical specifics] | [Stock/Market response] |

IMPORTANT: In the SOURCES column, list the citation numbers (e.g., [1, 3, 5]) that support this news. These should match the numbered references at the end of your report.

Cover 10 DISTINCT technology stories from:
- AI/ML developments
- Major tech company news
- Cybersecurity updates
- Product launches
- Research breakthroughs
- Industry trends
- Startup funding rounds
- Tech policy and regulation
- Open source projects
- Hardware innovations

Each row should be a SEPARATE tech news item, not multiple angles of the same story.

Focus on developments with significant industry impact.

REQUIREMENTS:
- Only include real announcements from verified sources
- Do not fabricate company news or technical details
- If insufficient news is found, include fewer rows rather than inventing content`
    },
    'local-news': {
        name: 'Local News',
        query: `LOCATION: [ENTER YOUR CITY/REGION HERE]

Find UP TO 10 LOCAL NEWS ITEMS for the specified location from today YYYY-MM-DD.

START YOUR RESPONSE DIRECTLY WITH THE TABLE. NO INTRODUCTION.

CRITICAL: All information MUST be from real, verifiable local sources. DO NOT invent or fabricate any local news, events, or details. Only report what you find from actual local news sources.

OUTPUT FORMAT:
| SOURCE | DATE | HEADLINE | CATEGORY | DETAILS | IMPACT ON COMMUNITY |
|--------|------|----------|----------|---------|---------------------|
| [Citation numbers] | [YYYY-MM-DD] | [Brief headline] | [Local Gov/Crime/Business/Community/Weather] | [What happened] | [How it affects residents] |

IMPORTANT: In the SOURCE column, list the citation numbers (e.g., [1, 3, 5]) that support this news. These should match the numbered references at the end of your report.

Include UP TO 10 DISTINCT local stories from:
- Local government decisions
- Community events
- Crime and safety updates
- Business openings/closures
- Infrastructure changes
- Weather impacts
- School and education news
- Local sports and recreation
- Health and public services
- Transportation updates

Each row should be a SEPARATE local event, not multiple updates about the same story.

IMPORTANT: Focus ONLY on news from the location specified at the top.

REQUIREMENTS:
- Only use real local news from verified sources
- Do not invent local events or incidents
- If insufficient local news is found, include fewer rows rather than fabricating content`
    },
    'topic-news': {
        name: 'Topic News',
        query: `TOPIC: [ENTER YOUR TOPIC HERE - e.g., anime, space exploration, renewable energy, etc.]

Find UP TO 10 DISTINCT, SEPARATE news stories about the specified topic from today YYYY-MM-DD.

CRITICAL INSTRUCTION: Each row MUST be about a COMPLETELY DIFFERENT news item. Do NOT include multiple articles, updates, or perspectives about the same story. For example, if searching for anime news:
- ✗ BAD: Three rows about the same My Hero Academia announcement (initial news, fan reactions, industry analysis)
- ✓ GOOD: One row each for: My Hero Academia news, Attack on Titan update, new Crunchyroll series, Studio Ghibli announcement, etc.

START YOUR RESPONSE DIRECTLY WITH THE TABLE. NO INTRODUCTION OR PREAMBLE.

CRITICAL: All information MUST be from real, verifiable sources. DO NOT invent or fabricate any news, events, or details. Only report what you find from actual sources.

OUTPUT FORMAT: Begin immediately with a markdown table using this exact structure:

| SOURCES | DATE | HEADLINE | SUBTOPIC | KEY DEVELOPMENTS | SIGNIFICANCE | IMPACT |
|---------|------|----------|----------|------------------|--------------|--------|
| [Citation numbers] | [YYYY-MM-DD] | [Descriptive headline] | [Specific aspect/genre] | [What happened - 3 sentences max] | [Why it matters to the community] | [1-10 score] |

IMPORTANT: In the SOURCES column, list the citation numbers (e.g., [1, 3, 5]) that support this news item. These should match the numbered references at the end of your report.

Example for anime topic:
| [1, 3] | 2025-01-07 | Studio Ghibli Announces New Film Project | Animation/Film | Legendary animation studio reveals first details about upcoming feature. Director Hayao Miyazaki returns from retirement again. Production scheduled to begin this spring. | Major event for anime industry and global animation fans. Could influence animation trends for years. | 8 |

SEARCH STRATEGY:
1. "[TOPIC]" news today YYYY-MM-DD latest announcement update
2. "[TOPIC]" breaking developments industry changes YYYY-MM-DD
3. site:specialized-sites.com "[TOPIC]" today YYYY-MM-DD (use relevant specialized sites for the topic)
4. "[TOPIC]" different various multiple separate news YYYY-MM-DD

DIVERSITY REQUIREMENTS - MUST find news about:
- DIFFERENT companies/studios/creators (not all from the same source)
- DIFFERENT products/series/projects (not all about the same thing)
- DIFFERENT aspects of the topic (technology, business, culture, events, etc.)
- DIFFERENT geographic regions if applicable

Focus areas (adapt based on topic) - aim for 10 DISTINCT news items:
- Major announcements or releases
- Industry developments
- Community events or conventions
- Creator/artist news
- Technology or innovation updates
- Cultural impact stories
- Business and market news
- Fan community highlights
- Awards and recognition
- Controversies or debates
- International developments
- Future predictions or trends

Each row MUST be a COMPLETELY SEPARATE news story, not multiple angles of the same event.

REQUIREMENTS:
- Only include real news from verified sources
- Sort by relevance and impact to the topic community
- Each row must be complete with all columns filled
- Keep focus on news that matters to enthusiasts of this topic
- If insufficient news is found, include fewer rows rather than inventing content

After the table, add:
- **COMMUNITY PULSE**: One paragraph about how the [TOPIC] community is reacting to today's developments
- **UPCOMING**: 3 bullet points about what to watch for in this topic area`
    },
    'product-prices': {
        name: 'Product Price Search',
        query: `PRODUCT PRICE SEARCH:
- Product: [PRODUCT]
- Location: [LOCATION]
- Marketplace: [MARKETPLACE]
- Price Range: [PRICE_RANGE]
- Condition: [CONDITION]

Find UP TO 10 current listings for the specified product in the given location from today YYYY-MM-DD.

CRITICAL: All listings MUST be from real, verifiable sources. DO NOT invent or fabricate any prices, sellers, or listing details.

OUTPUT FORMAT: Begin immediately with a markdown table using this exact structure:
| SOURCES | DATE | SELLER/STORE | PRICE | CONDITION | DESCRIPTION | SHIPPING | LINK |
|---------|------|--------------|-------|-----------|-------------|----------|------|
| [Citation numbers] | [YYYY-MM-DD] | [Seller name] | [$XXX] | [New/Used/Refurbished] | [Product details] | [Cost/Method] | [Platform] |

IMPORTANT: In the SOURCES column, list the citation numbers that support this listing.

Example row:
| [2, 5] | 2025-01-12 | TechStore123 | $299 | Used - Like New | iPhone 12 64GB Unlocked, minor scratches | Free shipping | eBay |

SEARCH STRATEGY:
1. "[PRODUCT]" price [MARKETPLACE] [LOCATION] for sale YYYY-MM-DD
2. site:[MARKETPLACE].com "[PRODUCT]" [LOCATION] buy [PRICE_RANGE]
3. "[PRODUCT]" [CONDITION] sale near [LOCATION] current listing
4. marketplace "[PRODUCT]" available [LOCATION] ship to

PRIORITIZE LISTINGS BY:
- Price (lowest to highest within specified range)
- Seller rating and reputation
- Shipping cost and speed
- Product condition matching requirements
- Location proximity

MARKETPLACE COVERAGE:
- eBay listings
- Facebook Marketplace
- Amazon (if specified)
- Craigslist (local only)
- Mercari
- OfferUp/Letgo
- Specialized marketplaces for the product type

REQUIREMENTS:
- Only include real, active listings
- Verify prices are current
- Include shipping costs when available
- Note seller ratings if provided
- Distinguish between auction and fixed prices
- Include return policies if mentioned

After the table, add:
- **PRICE ANALYSIS**: Average price, price range, and best value options
- **BUYING TIPS**: 3 recommendations for purchasing this product in [LOCATION]
- **ALTERNATIVES**: Similar products or models to consider`
    }
};

// Use news template
function useNewsTemplate(templateId) {
    if (templateId === 'custom') {
        // For custom template, open subscription modal with empty query
        // For custom template, redirect to new subscription page
        window.location.href = '/news/subscriptions/new';
        return;
    }

    const template = newsTemplates[templateId];
    if (template) {
        // DON'T replace YYYY-MM-DD here - keep it as a placeholder for the subscription
        let updatedQuery = template.query;

        // For topic news template, prompt for topic
        if (templateId === 'topic-news') {
            const topic = prompt('Enter the topic you want to track (e.g., anime, space exploration, renewable energy):');
            if (!topic) {
                return; // User cancelled
            }
            // Replace the topic placeholder and all instances of [TOPIC]
            updatedQuery = updatedQuery.replace(/\[ENTER YOUR TOPIC HERE[^\]]*\]/g, topic);
            updatedQuery = updatedQuery.replace(/\[TOPIC\]/g, topic);
        }

        // For local news template, prompt for location
        if (templateId === 'local-news') {
            const location = prompt('Enter your city or region:');
            if (!location) {
                return; // User cancelled
            }
            updatedQuery = updatedQuery.replace(/\[ENTER YOUR CITY\/REGION HERE\]/g, location);
        }
        // For product price search template, prompt for multiple inputs
        if (templateId === 'product-prices') {
            const product = prompt('Enter the product you want to search for:');
            if (!product) {
                return; // User cancelled
            }

            const location = prompt('Enter your city or region (e.g., "New York, NY" or "Los Angeles area"):');
            if (!location) {
                return; // User cancelled
            }

            const marketplace = prompt('Enter marketplace (optional - e.g., eBay, Facebook Marketplace, Amazon, or leave empty for all):') || 'all marketplaces';

            const priceRange = prompt('Enter price range (optional - e.g., "$100-$500" or leave empty for any price):') || 'any price';

            const condition = prompt('Enter condition (optional - e.g., New, Used, Refurbished, or leave empty for any):') || 'any condition';

            // Replace all placeholders
            updatedQuery = updatedQuery.replace(/\[PRODUCT\]/g, product);
            updatedQuery = updatedQuery.replace(/\[LOCATION\]/g, location);
            updatedQuery = updatedQuery.replace(/\[MARKETPLACE\]/g, marketplace);
            updatedQuery = updatedQuery.replace(/\[PRICE_RANGE\]/g, priceRange);
            updatedQuery = updatedQuery.replace(/\[CONDITION\]/g, condition);
        }

        // Open subscription modal with prefilled template
        // Add a note to show user that dates will be dynamic
        const modalName = template.name + (updatedQuery.includes('YYYY-MM-DD') ? ' (with dynamic dates)' : '');
        // Redirect to new subscription page with prefilled template
        const params = new URLSearchParams({
            query: updatedQuery,
            name: modalName,
            template: templateId
        });
        // The destination is the hardcoded literal path /news/subscriptions/new;
        // only the query string is dynamic, so the origin/host cannot be
        // attacker-controlled -- Bearer false positive (not an open redirect).
        // bearer:disable javascript_lang_open_redirect
        window.location.href = `/news/subscriptions/new?${params.toString()}`;
    }
}

// Show subscription modal for news templates
function showNewsSubscriptionModal(query = '', templateName = '') {
    SafeLogger.log('showNewsSubscriptionModal called with:', query, templateName);

    // Create modal HTML if it doesn't exist
    if (!document.getElementById('newsSubscriptionModal')) {
        SafeLogger.log('Creating modal HTML');
        const modalHtml = `
            <div class="modal fade" id="newsSubscriptionModal" tabindex="-1">
                <div class="modal-dialog">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title">Create News Subscription</h5>
                            <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                        </div>
                        <form id="news-subscription-form">
                            <div class="modal-body">
                                <div class="mb-3">
                                    <label for="news-subscription-query" class="form-label">Topic or Search Query</label>
                                    <textarea class="ldr-form-control" id="news-subscription-query" rows="4"
                                           placeholder="e.g., artificial intelligence, climate change, tech news" required></textarea>
                                    <div class="form-text">Enter keywords, topics, or search queries you want to track</div>
                                </div>

                                <div class="mb-3">
                                    <label for="news-subscription-name" class="form-label">Subscription Name (Optional)</label>
                                    <input type="text" class="ldr-form-control" id="news-subscription-name"
                                           placeholder="e.g., AI Research Updates">
                                </div>

                                <div class="row">
                                    <div class="col-md-6 mb-3">
                                        <label for="news-subscription-frequency" class="form-label">Update Frequency</label>
                                        <select class="form-select" id="news-subscription-frequency">
                                            <option value="hourly">Every Hour</option>
                                            <option value="daily" selected>Daily</option>
                                            <option value="weekly">Weekly</option>
                                        </select>
                                    </div>

                                    <div class="col-md-6 mb-3">
                                        <label for="news-subscription-folder" class="form-label">Folder</label>
                                        <select class="form-select" id="news-subscription-folder">
                                            <option value="">Uncategorized</option>
                                            <!-- Dynamic folders will be added here -->
                                        </select>
                                    </div>
                                </div>

                                <div class="mb-3">
                                    <div class="form-check">
                                        <input class="form-check-input" type="checkbox" id="news-subscription-active" checked>
                                        <label class="form-check-label" for="news-subscription-active">
                                            Start active (begin collecting news immediately)
                                        </label>
                                    </div>
                                </div>

                                <div class="mb-3">
                                    <div class="form-check">
                                        <input class="form-check-input" type="checkbox" id="news-subscription-run-now" checked>
                                        <label class="form-check-label" for="news-subscription-run-now">
                                            Run immediately after creating
                                        </label>
                                    </div>
                                </div>
                            </div>
                            <div class="modal-footer">
                                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
                                <button type="button" class="btn btn-primary" id="run-template-btn">Run Once</button>
                                <button type="submit" class="btn btn-primary">Create Subscription</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        `;
        // eslint-disable-next-line no-unsanitized/method -- audited 2026-03-28: HTML variable built with escaped values
        document.body.insertAdjacentHTML('beforeend', modalHtml);

        // Set up form submission handler
        document.getElementById('news-subscription-form').addEventListener('submit', handleNewsSubscriptionSubmit);

        // Set up run once button
        document.getElementById('run-template-btn').addEventListener('click', async () => {
            const currentQuery = document.getElementById('news-subscription-query').value;
            SafeLogger.log('Run Once clicked, query:', currentQuery);
            if (currentQuery) {
                // Close modal first
                bootstrap.Modal.getInstance(document.getElementById('newsSubscriptionModal')).hide();

                // Use the same advanced search function that the search uses
                SafeLogger.log('Calling performAdvancedNewsSearch with query:', currentQuery);
                await performAdvancedNewsSearch(currentQuery);
            } else {
                SafeLogger.error('No query found in news-subscription-query input');
                showAlert('Please enter a query', 'warning');
            }
        });
    }

    // Prefill the form
    document.getElementById('news-subscription-query').value = query;
    document.getElementById('news-subscription-name').value = templateName;

    // Load folders
    loadSubscriptionFolders();

    // Initialize the custom dropdown for model selection
    // TODO: This appears to be a typo — templates define `initializeDropdowns`
    // (plural) but this calls `initializeDropdown` (singular). The typeof
    // guard masks the bug; the dropdown likely never initialises here.
    // Investigate separately; not changing behaviour as part of a lint PR.
    // eslint-disable-next-line no-undef -- see TODO above
    if (typeof initializeDropdown !== 'undefined') {
        // eslint-disable-next-line no-undef
        initializeDropdown('news-subscription-model', 'news-model-dropdown', 'model');
    }

    // Show the modal
    try {
        SafeLogger.log('Attempting to show modal');
        if (typeof bootstrap === 'undefined') {
            SafeLogger.error('Bootstrap is not loaded!');
            alert('Bootstrap is not loaded. Please refresh the page.');
            return;
        }
        const modalElement = document.getElementById('newsSubscriptionModal');
        if (!modalElement) {
            SafeLogger.error('Modal element not found!');
            return;
        }
        const modal = new bootstrap.Modal(modalElement);
        modal.show();
        SafeLogger.log('Modal should be visible now');
    } catch (error) {
        SafeLogger.error('Error showing modal:', error);
        alert('Error showing subscription modal: ' + error.message);
    }
}

// Handle news subscription form submission
async function handleNewsSubscriptionSubmit(e) {
    e.preventDefault();

    const query = document.getElementById('news-subscription-query').value;
    const name = document.getElementById('news-subscription-name').value || query.substring(0, 50);
    const frequency = document.getElementById('news-subscription-frequency').value;
    const folder = document.getElementById('news-subscription-folder').value;
    const isActive = document.getElementById('news-subscription-active').checked;
    const runNow = document.getElementById('news-subscription-run-now').checked;

    // Model configuration - get from custom dropdown
    const modelInput = document.getElementById('news-subscription-model');
    const model = modelInput ? modelInput.value : '';

    // Extract provider from the model selection (e.g., "OLLAMA:llama3" -> "OLLAMA")
    let modelProvider = '';
    if (model && model.includes(':')) {
        const parts = model.split(':');
        modelProvider = parts[0];
    }

    const searchStrategy = document.getElementById('news-subscription-strategy').value;

    try {
        // Create the subscription
        const response = await fetch('/news/api/subscribe', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': getCSRFToken()
            },
            body: JSON.stringify({
                query,
                name,
                subscription_type: 'search',
                refresh_minutes: parseInt(frequency, 10),
                folder_id: folder || null,
                is_active: isActive,
                model_provider: modelProvider,
                model,
                search_strategy: searchStrategy
            })
        });

        if (response.ok) {
            const data = await response.json();
            showAlert('Subscription created successfully!', 'success');

            // Close modal
            bootstrap.Modal.getInstance(document.getElementById('newsSubscriptionModal')).hide();

            // Run immediately if requested
            if (runNow && data.subscription_id) {
                // Call the run endpoint for this subscription
                try {
                    const runResponse = await fetch(`/news/api/subscriptions/${data.subscription_id}/run`, {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                            'X-CSRFToken': getCSRFToken()
                        }
                    });

                    if (runResponse.ok) {
                        const runData = await runResponse.json();

                        if (runData.research_id) {
                            showAlert('Subscription research started...', 'info');

                            // Show loading state in news feed with progress visualization
                            const container = document.getElementById('news-feed-content');
                            const progressCard = document.createElement('div');
                            progressCard.className = 'ldr-news-card ldr-priority-high ldr-active-research-card';
                            // bearer:disable javascript_lang_dangerous_insert_html
                            progressCard.innerHTML = `
                                <div class="ldr-news-header">
                                    <h2 class="ldr-news-title">
                                        <i class="bi bi-hourglass-split ldr-spinning"></i>
                                        Running subscription: "${escapeHtml(query.substring(0, 60))}..."
                                    </h2>
                                </div>
                                <div class="ldr-news-meta">
                                    <span><i class="bi bi-info-circle"></i> Research ID: ${escapeHtml(runData.research_id)}</span>
                                    <span><i class="bi bi-clock"></i> Started just now</span>
                                </div>
                                <div class="ldr-news-summary">
                                    <div class="progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="10" aria-label="Subscription progress">
                                        <div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 10%"></div>
                                    </div>
                                    <p class="mt-2">Searching for breaking news and analyzing importance...</p>
                                </div>
                            `;

                            // Insert at the top of the feed
                            container.insertBefore(progressCard, container.firstChild);

                            // Poll for results
                            pollForNewsResearchResults(runData.research_id, query);
                        }
                    } else {
                        SafeLogger.error('Failed to run subscription immediately');
                    }
                } catch (error) {
                    SafeLogger.error('Error running subscription:', error);
                }
            }

            // Update sidebar subscriptions
            loadSubscriptions();
        } else {
            const error = await response.json();
            showAlert(error.error || 'Failed to create subscription', 'danger');
        }
    } catch (error) {
        SafeLogger.error('Error creating subscription:', error);
        showAlert('Failed to create subscription', 'danger');
    }
}

// Load subscription folders for the modal
async function loadSubscriptionFolders() {
    try {
        const response = await fetch('/news/api/subscription/folders');
        if (response.ok) {
            const folders = await response.json();
            const select = document.getElementById('news-subscription-folder');

            // Clear existing options except the first one
            select.innerHTML = '<option value="">Uncategorized</option>';

            // Add folder options
            folders.forEach(folder => {
                const option = document.createElement('option');
                option.value = folder.id;
                option.textContent = `${folder.icon || '📁'} ${folder.name}`;
                select.appendChild(option);
            });
        }
    } catch (error) {
        SafeLogger.error('Error loading folders:', error);
    }
}

// Show custom template modal
function showCustomTemplateModal() {
    const modalHtml = `
        <div class="modal fade" id="customTemplateModal" tabindex="-1">
            <div class="modal-dialog modal-lg">
                <div class="modal-content">
                    <div class="modal-header">
                        <h5 class="modal-title">Create Custom News Template</h5>
                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                    </div>
                    <div class="modal-body">
                        <div class="mb-3">
                            <label class="form-label">Template Name</label>
                            <input type="text" class="ldr-form-control" id="template-name" placeholder="e.g., Industry Analysis">
                        </div>
                        <div class="mb-3">
                            <label class="form-label">Search Query Template</label>
                            <textarea class="ldr-form-control" id="template-query" rows="10" placeholder="Enter your custom news search query..."></textarea>
                        </div>
                        <div class="alert alert-info">
                            <i class="bi bi-info-circle"></i> Tips:
                            <ul class="mb-0 mt-2">
                                <li>Use markdown tables for structured output</li>
                                <li>Include specific search instructions</li>
                                <li>Define clear column headers</li>
                                <li>Specify date ranges and sources</li>
                            </ul>
                        </div>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
                        <button type="button" class="btn btn-primary" onclick="applyCustomTemplate()">Use Template</button>
                    </div>
                </div>
            </div>
        </div>
    `;

    // Remove existing modal if any
    const existingModal = document.getElementById('customTemplateModal');
    if (existingModal) {
        existingModal.remove();
    }

    // Add modal to body
    document.body.insertAdjacentHTML('beforeend', modalHtml);

    // Show modal
    const modal = new bootstrap.Modal(document.getElementById('customTemplateModal'));
    modal.show();
}

// Apply custom template
function applyCustomTemplate() {
    const templateName = document.getElementById('template-name').value;
    const templateQuery = document.getElementById('template-query').value;

    if (!templateQuery.trim()) {
        showAlert('Please enter a template query', 'warning');
        return;
    }

    // Set the query
    const searchInput = document.getElementById('news-search-input');
    searchInput.value = templateQuery;

    // Close modal
    const modal = bootstrap.Modal.getInstance(document.getElementById('customTemplateModal'));
    modal.hide();

    // Show message
    showAlert(`Custom template "${templateName || 'Untitled'}" loaded. Click "Search News" to run.`, 'success');
}

// Expose functions to global scope for onclick handlers
window.filterByTopic = filterByTopic;
window.clearTopicFilter = clearTopicFilter;
window.clearAllFilters = clearAllFilters;
window.createTopicSubscription = createTopicSubscription;
window.useNewsTemplate = useNewsTemplate;
window.applyCustomTemplate = applyCustomTemplate;
window.vote = vote;
window.saveItem = saveItem;
window.loadNewsTableQuery = loadNewsTableQuery;
window.markAsReadOnClick = markAsReadOnClick;
window.hideQueryTemplate = hideQueryTemplate;
window.useQueryTemplate = useQueryTemplate;
window.copyQueryTemplate = copyQueryTemplate;
window.refreshFeed = refreshFeed;
window.rerunSearch = rerunSearch;
window.toggleReadStatus = toggleReadStatus;
window.markAllAsRead = markAllAsRead;
window.toggleExpanded = toggleExpanded;
window.expandAll = expandAll;
window.collapseAll = collapseAll;
window.shareNews = shareNews;
window.copyNewsLink = copyNewsLink;
window.exportToMarkdown = exportToMarkdown;
window.hideNewsItem = hideNewsItem;
window.toggleSaveItem = toggleSaveItem;
window.selectSubscription = selectSubscription;
// window.filterBySubscription = filterBySubscription; // Function not implemented yet
window.showCreateSubscriptionModal = showCreateSubscriptionModal;
window.createSubscription = createSubscription;
window.createSubscriptionFromItem = createSubscriptionFromItem;
// Exposed for test harness: lets XSS-regression tests drive a single render
// cycle with a controlled newsItems payload without going through the full
// DOMContentLoaded -> initializeNewsPage chain.
window.loadNewsFeed = loadNewsFeed;
// Exposed for test harness: lets the search-history XSS-regression test
// drive a single render cycle with a controlled payload.
window.loadSearchHistory = loadSearchHistory;
